diff --git a/packages/neuron/next.config.mjs b/packages/neuron/next.config.mjs index d19af54..bb4da43 100644 --- a/packages/neuron/next.config.mjs +++ b/packages/neuron/next.config.mjs @@ -29,6 +29,17 @@ const config = { ], }, + sassOptions: { + logger: { + warn: function (message) { + console.warn(message) + }, + debug: function (message) { + console.log(message) + }, + }, + }, + webpack(config) { config.module.rules.push({ test: /\.svg$/i, diff --git a/packages/neuron/public/locales/en/common.json b/packages/neuron/public/locales/en/common.json index 6d00725..8d4ad2f 100644 --- a/packages/neuron/public/locales/en/common.json +++ b/packages/neuron/public/locales/en/common.json @@ -1,15 +1,15 @@ { + "About Neuron": "About Neuron", + "Advanced Features": "Advanced Features", "All services are online": "All services are online", "Announcement": "Announcement", "Assets": "Assets", "Axon Explorer": "Axon Explorer", - "Backup wallet": "Backup wallet", "Beginner's Guide": "Beginner's Guide", "Change log": "Change log", "Changelog": "Changelog", "CKB Explorer": "CKB Explorer", "Copyright © 2023 Magickbase All Rights Reserved.": "Copyright © 2023 Magickbase All Rights Reserved.", - "Create wallet": "Create wallet", "Develop guide": "Develop guide", "Download Neuron": "Download Neuron", "Foundation": "Foundation", @@ -20,14 +20,17 @@ "Kuai": "Kuai", "Language": "Language", "Nervos": "Nervos", + "Neuron": "Neuron", "Neuron Wallet": "Neuron Wallet", "Public Node": "Public Node", "Report": "Report", "Safety": "Safety", + "Service Monitor": "Service Monitor", "Services": "Services", "Services status loading...": "Services status loading...", "Some services are offline": "Some services are offline", + "Status": "Status", "Sync": "Sync", "Transaction": "Transaction", - "Transfer and receive": "Transfer and receive" + "Usage Tutorial": "Usage Tutorial" } diff --git a/packages/neuron/public/locales/zh/common.json b/packages/neuron/public/locales/zh/common.json index d52171f..6e6b23f 100644 --- a/packages/neuron/public/locales/zh/common.json +++ b/packages/neuron/public/locales/zh/common.json @@ -1,15 +1,15 @@ { + "About Neuron": "关于 Neuron", + "Advanced Features": "高级功能", "All services are online": "所有服务均可用", "Announcement": "公告", - "Assets": "Assets", + "Assets": "资产", "Axon Explorer": "Axon 区块浏览器", - "Backup wallet": "备份钱包", "Beginner's Guide": "新手指南", "Change log": "更新日志", "Changelog": "更新日志", "CKB Explorer": "CKB 区块浏览器", "Copyright © 2023 Magickbase All Rights Reserved.": "Copyright © 2023 Magickbase 版权所有", - "Create wallet": "创建钱包", "Develop guide": "开发指南", "Download Neuron": "下载 Neuron", "Foundation": "基金会", @@ -20,14 +20,17 @@ "Kuai": "Kuai", "Language": "语言", "Nervos": "Nervos", + "Neuron": "Neuron", "Neuron Wallet": "Neuron 钱包", "Public Node": "公共节点", - "Report": "Report", - "Safety": "Safety", + "Report": "报告", + "Safety": "安全", + "Service Monitor": "服务监控", "Services": "服务", - "Services status loading...": "Services status loading...", - "Some services are offline": "Some services are offline", + "Services status loading...": "服务状态加载中...", + "Some services are offline": "部分服务不可用", + "Status": "状态", "Sync": "同步", "Transaction": "交易", - "Transfer and receive": "转账和接收" + "Usage Tutorial": "使用教程" } diff --git a/packages/neuron/src/components/Button/index.module.scss b/packages/neuron/src/components/Button/index.module.scss new file mode 100644 index 0000000..7d3e63b --- /dev/null +++ b/packages/neuron/src/components/Button/index.module.scss @@ -0,0 +1,76 @@ +@use 'sass:color'; + +.button { + all: unset; + display: flex; + gap: 10px; + align-items: center; + justify-content: center; + padding: 16px 24px; + border-radius: 7px; + cursor: pointer; +} + +.contained { + color: var(--btnForeground); + background: var(--btnBackground); + + &:disabled { + color: var(--btnForegroundDisabled); + background: var(--btnBackgroundDisabled); + } + + &:not(:disabled):hover { + background: var(--btnBackgroundHover); + } + + &:not(:disabled):active { + background: var(--btnBackgroundActive); + } +} + +.outlined { + color: var(--btnBackground); + border: 2px solid var(--btnBackground); + + &:disabled { + color: var(--btnBackgroundDisabled); + border-color: var(--btnBackgroundDisabled); + } + + &:not(:disabled):hover { + color: var(--btnBackgroundHover); + border-color: var(--btnBackgroundHover); + } + + &:not(:disabled):active { + color: var(--btnBackgroundActive); + border-color: var(--btnBackgroundActive); + } +} + +.text { + padding: 6px 24px; + color: var(--btnBackground); + + &:disabled { + color: var(--btnBackgroundDisabled); + } + + &:not(:disabled):hover { + color: var(--btnBackgroundHover); + } + + &:not(:disabled):active { + color: var(--btnBackgroundActive); + } +} + +.themeBlackWhite { + --btnForeground: #000; + --btnForegroundDisabled: #{rgba(#000, 0.5)}; + --btnBackground: #f5f5f5; + --btnBackgroundDisabled: #{color.mix(#fff, #f5f5f5, $weight: 50%)}; + --btnBackgroundHover: #{color.mix(#000, #f5f5f5, $weight: 20%)}; + --btnBackgroundActive: #{color.mix(#000, #f5f5f5, $weight: 40%)}; +} diff --git a/packages/neuron/src/components/Button/index.tsx b/packages/neuron/src/components/Button/index.tsx new file mode 100644 index 0000000..99876b3 --- /dev/null +++ b/packages/neuron/src/components/Button/index.tsx @@ -0,0 +1,38 @@ +/** + * Currently, there are no complex button scenarios, so a simple implementation is sufficient. + * If the requirements cannot be met in the future, consider using a third-party component library. + */ +import { ComponentProps, FC } from 'react' +import clsx from 'clsx' +import styles from './index.module.scss' +import presets from '../../styles/presets.module.scss' + +export type ButtonVariant = 'contained' | 'outlined' | 'text' + +export interface ButtonProps extends ComponentProps<'button'> { + variant?: ButtonVariant + theme?: 'light' | 'dark' | 'blackwhite' +} + +export const Button: FC = ({ children, variant = 'contained', theme, ...elProps }) => { + return ( + + ) +} diff --git a/packages/neuron/src/components/Contacts/index.tsx b/packages/neuron/src/components/Contacts/index.tsx index ffa2ea0..1b3d5a8 100644 --- a/packages/neuron/src/components/Contacts/index.tsx +++ b/packages/neuron/src/components/Contacts/index.tsx @@ -1,7 +1,7 @@ import { ComponentProps, FC } from 'react' import Link from 'next/link' import clsx from 'clsx' -import IconTwitter from './twitter.svg' +import IconX from './x.svg' import IconDiscord from './discord.svg' import IconGithub from './github.svg' import styles from './index.module.scss' @@ -14,7 +14,7 @@ export const Contacts: FC & { linkClass?: string; iconClas return (
- + diff --git a/packages/neuron/src/components/Contacts/twitter.svg b/packages/neuron/src/components/Contacts/twitter.svg deleted file mode 100644 index cccc967..0000000 --- a/packages/neuron/src/components/Contacts/twitter.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/neuron/src/components/Contacts/x.svg b/packages/neuron/src/components/Contacts/x.svg new file mode 100644 index 0000000..e4779c0 --- /dev/null +++ b/packages/neuron/src/components/Contacts/x.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/neuron/src/components/Footer/index.module.scss b/packages/neuron/src/components/Footer/index.module.scss index 5d8368e..0a62d47 100644 --- a/packages/neuron/src/components/Footer/index.module.scss +++ b/packages/neuron/src/components/Footer/index.module.scss @@ -5,7 +5,7 @@ border-top: 1px solid #2e2e2e; @media (max-width: $mobileBreakPoint) { - padding: 50px var(--contentWrapperPadding) 70px var(--contentWrapperPadding); + padding: 48px var(--contentWrapperPadding) 104px var(--contentWrapperPadding); } .content { @@ -38,16 +38,22 @@ .serversState { display: flex; - gap: 8px; + gap: 4px; align-items: center; margin-top: 24px; color: #00cc9b; font-size: 14px; line-height: 16px; + .title { + color: #f5f5f5; + font-weight: 600; + } + .dot { width: 6px; height: 6px; + margin-left: 4px; background: #00cc9b; border-radius: 50%; } @@ -60,6 +66,26 @@ background: chocolate; } } + + @media (max-width: $mobileBreakPoint) { + margin-top: 40px; + + .title { + font-size: 16px; + } + } +} + +.serviceMonitor { + margin-top: 24px; + color: #f5f5f5; + font-weight: 600; + font-size: 14px; + line-height: 16px; + + @media (max-width: $mobileBreakPoint) { + font-size: 16px; + } } .contacts { diff --git a/packages/neuron/src/components/Footer/index.tsx b/packages/neuron/src/components/Footer/index.tsx index 89e5357..179f8c6 100644 --- a/packages/neuron/src/components/Footer/index.tsx +++ b/packages/neuron/src/components/Footer/index.tsx @@ -35,12 +35,15 @@ export const Footer: FC = props => { aggregateStateQuery.data === 'downtime' || aggregateStateQuery.data === 'degraded', })} > + {t('Status')}
{serviceStateText}
+
{t('Service Monitor')}
+ {!isMobile &&
{t('Copyright © 2023 Magickbase All Rights Reserved.')}
} diff --git a/packages/neuron/src/components/Header/index.tsx b/packages/neuron/src/components/Header/index.tsx index a5abc3c..61d240e 100644 --- a/packages/neuron/src/components/Header/index.tsx +++ b/packages/neuron/src/components/Header/index.tsx @@ -101,6 +101,13 @@ const MenuDialog: FC = () => {
+
{t('Neuron')}
+
+ {t('Changelog')} + {t('Help Center')} + {t('Download Neuron')} +
+ {t('Home')} diff --git a/packages/neuron/src/components/Page/index.tsx b/packages/neuron/src/components/Page/index.tsx index 411920d..5fb0d8b 100644 --- a/packages/neuron/src/components/Page/index.tsx +++ b/packages/neuron/src/components/Page/index.tsx @@ -3,6 +3,8 @@ import { clsx } from 'clsx' import { Footer, FooterProps } from '../Footer' import { Header, HeaderProps } from '../Header' import styles from './index.module.scss' +import { useBodyClass } from '../../hooks' +import presets from '../../styles/presets.module.scss' type PageProps = Omit, 'children'> & { children?: @@ -18,6 +20,13 @@ export const Page = forwardRef(function Page(props, r const { children, contentWrapper = true, className, ...divProps } = props const contentWrapperProps = typeof contentWrapper === 'object' ? contentWrapper : {} + // _document.page.tsx adds a theme class name to the HTML element during initialization, but this class name remains fixed and does not change. + // In order to switch themes correctly when navigating between routes, it is necessary to add a theme class name to the body element. + // Currently, except for the post detail page, all other pages have a fixed dark mode and cannot switch color modes. + // Since the post detail page does not use the Page component, a fixed dark mode class name is applied here. + // TODO: This may not be a good code implementation, but we should consider refactoring it after having multiple pages with switchable color modes. + useBodyClass([presets.themeDark ?? '']) + const finalChildren = typeof children === 'function' ? ( children({ diff --git a/packages/neuron/src/components/TableOfContents/index.tsx b/packages/neuron/src/components/TableOfContents/index.tsx index ea72c1e..e8a9e8a 100644 --- a/packages/neuron/src/components/TableOfContents/index.tsx +++ b/packages/neuron/src/components/TableOfContents/index.tsx @@ -3,6 +3,7 @@ import React, { createContext, FC, ReactNode, + RefAttributes, RefCallback, useCallback, useContext, @@ -138,7 +139,15 @@ export const TOCContextProvider: FC<{ children?: ReactNode | ((value: TOCContext ) } -export const TOCItem: FC & { id: string; titleInTOC: string }> = props => { +type TOCElementProps = { id: string } + +export const TOCItem: FC< + Omit, 'children'> & + TOCElementProps & { + titleInTOC: string + children?: ReactNode | ((props: TOCElementProps & { ref: RefAttributes['ref'] }) => ReactNode) + } +> = props => { const { children, id, titleInTOC, ...divProps } = props const ref = useRef(null) @@ -152,8 +161,12 @@ export const TOCItem: FC & { id: string; titleInTOC: strin return () => removeTOCItem(el) }, [addTOCItem, removeTOCItem, titleInTOC]) - return ( -
+ const finalId = encodeURIComponent(id.toLowerCase().replaceAll(' ', '_')) + + return typeof children === 'function' ? ( + children({ ref, id: finalId }) + ) : ( +
{children}
) diff --git a/packages/neuron/src/components/UpsideDownEffect/index.module.scss b/packages/neuron/src/components/UpsideDownEffect/index.module.scss index a21f300..a9c3ae3 100644 --- a/packages/neuron/src/components/UpsideDownEffect/index.module.scss +++ b/packages/neuron/src/components/UpsideDownEffect/index.module.scss @@ -9,15 +9,11 @@ display: flex; flex-direction: column; align-items: center; - - // width: 100%; transition: transform 0.4s ease; .content, .cloned { display: block; - - // width: 100%; overflow: hidden; text-overflow: ellipsis; transition: transform 0.4s ease; diff --git a/packages/neuron/src/hooks/index.ts b/packages/neuron/src/hooks/index.ts index f5fd708..14e5d3b 100644 --- a/packages/neuron/src/hooks/index.ts +++ b/packages/neuron/src/hooks/index.ts @@ -7,6 +7,14 @@ export * from './useMemoizedFn' export * from './useMarkdownProps' export * from './useHighPrecisionScrollbarWidth' +// This logic is used to prevent some errors when Next.js hydration. +// https://nextjs.org/docs/messages/react-hydration-error#possible-ways-to-fix-it +export function useIsHydrated() { + const [isHydrated, setIsHydrated] = useState(false) + useEffect(() => setIsHydrated(true), []) + return isHydrated +} + /** * copied from https://usehooks-ts.com/react-hook/use-media-query */ @@ -52,12 +60,8 @@ export const useIsMobile = (ignoreHydrated?: boolean) => { if (mobileBreakPoint == null) throw new Error('Incorrect css variable') const isMobile = useMediaQuery(`(max-width: ${mobileBreakPoint})`) - - // This logic is used to prevent some errors when Next.js hydration. - // https://nextjs.org/docs/messages/react-hydration-error#possible-ways-to-fix-it - const [hydrated, setHydrated] = useState(ignoreHydrated ? true : false) - useEffect(() => setHydrated(true), []) - return isMobile && hydrated + const isHydrated = useIsHydrated() + return isMobile && (ignoreHydrated || isHydrated) } export function useDevicePixelRatio() { diff --git a/packages/neuron/src/hooks/useMarkdownProps.tsx b/packages/neuron/src/hooks/useMarkdownProps.tsx index c22bea2..a7938b1 100644 --- a/packages/neuron/src/hooks/useMarkdownProps.tsx +++ b/packages/neuron/src/hooks/useMarkdownProps.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { ComponentProps, useMemo } from 'react' +import { ComponentProps, ComponentPropsWithoutRef, useMemo } from 'react' import ReactMarkdown from 'react-markdown' -import { HeadingProps } from 'react-markdown/lib/ast-to-react' +import { HeadingProps, ReactMarkdownProps } from 'react-markdown/lib/ast-to-react' import remarkGfm from 'remark-gfm' import rehypeRaw from 'rehype-raw' import rehypeSanitize from 'rehype-sanitize' @@ -11,13 +11,18 @@ import { TOCItem } from '../components/TableOfContents' import { UpsideDownEffect } from '../components/UpsideDownEffect' type MarkdownProps = Omit, 'children'> +export type LinkComponentProps = ComponentPropsWithoutRef<'a'> & ReactMarkdownProps export function useMarkdownProps({ supportToc = true, imgClass, + linkClass, + disableLinkEffect, }: { supportToc?: boolean imgClass?: string + linkClass?: string + disableLinkEffect?: boolean | ((props: LinkComponentProps) => boolean) }): MarkdownProps { const components: MarkdownProps['components'] = useMemo( () => ({ @@ -30,15 +35,21 @@ export function useMarkdownProps({ h6: wrapHeadingWithTOCItem('h6'), }), - a: ({ node, children, ...tagProps }) => ( - - {children} - - ), + a: (props: LinkComponentProps) => { + const { node, children, ...tagProps } = props + const finalDisableLinkEffect = + typeof disableLinkEffect === 'function' ? disableLinkEffect(props) : disableLinkEffect + + return ( + + {finalDisableLinkEffect ? children : {children}} + + ) + }, img: ({ node, ...tagProps }) => ( // Expectedly, all the links are external (content from GitHub), so there is no need to use next/image. // eslint-disable-next-line @next/next/no-img-element - {tagProps.alt + {tagProps.alt ), table: ({ node, ...tagProps }) => ( // The table is too wide, so we need to wrap it in the OverlayScrollbarsComponent. @@ -47,7 +58,7 @@ export function useMarkdownProps({ ), }), - [imgClass, supportToc], + [disableLinkEffect, imgClass, linkClass, supportToc], ) const remarkPlugins = useMemo(() => [remarkGfm], []) @@ -56,14 +67,14 @@ export function useMarkdownProps({ return { remarkPlugins, rehypePlugins, components } } -function wrapHeadingWithTOCItem(HeadingTag: string) { - return function tagWithTOCItem({ node, ...tagProps }: HeadingProps) { +function wrapHeadingWithTOCItem(HeadingTag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6') { + return function tagWithTOCItem({ node, sourcePosition, index, siblingCount, ...tagProps }: HeadingProps) { const content = tagProps.children[0] if (typeof content !== 'string') return return ( - + {({ ref, id }) => } ) } diff --git a/packages/neuron/src/pages/changelog/[[...slug]].page.tsx b/packages/neuron/src/pages/changelog/[[...slug]].page.tsx index 52d847e..d3f7a82 100644 --- a/packages/neuron/src/pages/changelog/[[...slug]].page.tsx +++ b/packages/neuron/src/pages/changelog/[[...slug]].page.tsx @@ -10,7 +10,7 @@ import { Release, getReleases, range } from '../../utils' import { Page } from '../../components/Page' import styles from './index.module.scss' import ImgNeuronLogo from './neuron-logo.png' -import { useMarkdownProps } from '../../hooks' +import { LinkComponentProps, useMarkdownProps } from '../../hooks' import IconTop from './top.svg' import IconMore from './more.svg' import { LinkWithEffect } from '../../components/UpsideDownEffect' @@ -26,46 +26,57 @@ interface PageProps { const Changelog: NextPage = ({ releases, page, maxPage }) => { const { t } = useTranslation('changelog') - const mdProps = useMarkdownProps({ supportToc: false, imgClass: styles.img }) + const disableLinkEffect = useCallback( + // Apply the effect only to text-based links to avoid affecting elements like images. + ({ children }: LinkComponentProps) => children.some(child => typeof child !== 'string'), + [], + ) + const mdProps = useMarkdownProps({ + supportToc: false, + disableLinkEffect, + imgClass: styles.img, + linkClass: styles.link, + }) const gotoTop = useCallback(() => scrollTo({ top: 0, behavior: 'smooth' }), []) return ( -
-
- Neuron Logo - Neuron +
+
+
+ Neuron Logo + Neuron +
+ +
{t('Changelog')}
+ +
+ + ), + }} + /> +
-
{t('Changelog')}
- -
- - ), - }} - /> -
-
- -
{/* TODO: If we were to manually parse the required content from the release here, it would be too complex and not robust, so let's implement a simple solution for now and have the neuron team provide a file specifically for reading later. */} {releases.map((release, idx) => ( -
+
{`${release.tag_name.replace('v', '')} (${release.published_at?.split('T')[0] ?? ''})`}
-
+ +
{release.body?.replace(/^#[^#]*?\(.*?\)\s+/, '') ?? ''} {idx === releases.length - 1 && ( diff --git a/packages/neuron/src/pages/changelog/index.module.scss b/packages/neuron/src/pages/changelog/index.module.scss index 3d80ab8..87b8051 100644 --- a/packages/neuron/src/pages/changelog/index.module.scss +++ b/packages/neuron/src/pages/changelog/index.module.scss @@ -63,22 +63,29 @@ } } -.releases { +.layout { display: grid; grid-template-columns: max-content 1fr; gap: 32px 0; - margin-top: 56px; + margin-top: 64px; - .left { - flex-shrink: 0; + --lineHeight: 38px; + + .top { + grid-column: 2; + padding-bottom: 24px; + } + + .releaseVersion { padding-top: 48px; padding-right: 40px; font-weight: 600; font-size: 18px; + line-height: var(--lineHeight); border-top: 1px solid #333; } - .right { + .releaseDescription { min-width: 0; padding-top: 48px; word-break: break-word; @@ -88,25 +95,99 @@ margin-top: 0; } + p, + li, + h2, + h3, + h4 { + line-height: var(--lineHeight); + } + + p, + ul { + margin: 0; + } + + ul { + padding: 0; + list-style-type: none; + } + + h2, + h3, + h4 { + margin: 24px 0 8px; + font-weight: 400; + font-size: 24px; + + &:first-child { + margin-top: 0; + } + } + + hr { + margin: 24px 0; + border: 0; + border-bottom: 1px solid #333; + } + + hr + * { + margin-top: 0; + } + table { word-break: initial; - } - a { - color: #00c891; + th, + td { + padding: 4px 16px 4px 0; + } + + th { + padding-bottom: 12px; + } + + th:not(:last-child) { + text-align: left; + } } - img { - max-width: 100%; + @media (max-width: $mobileBreakPoint) { + h2, + h3, + h4 { + margin: 32px 0 16px; + font-size: 22px; + } + + // Writing it again here is to address the issue of insufficient specificity caused by the media selector. + hr + * { + margin-top: 0; + } } } + .img { + max-width: 100%; + } + + .link { + color: #00c891; + } + @media (max-width: $mobileBreakPoint) { grid-template-columns: 1fr; gap: 24px; - margin-top: 24px; + margin-top: 16px; + + --lineHeight: 32px; + + .top { + grid-column: 1; + padding-bottom: 0; + } - .left { + .releaseVersion { padding-top: 32px; padding-right: 0; color: #999; @@ -114,7 +195,7 @@ font-size: 16px; } - .right { + .releaseDescription { padding-top: 0; border-top: 0; } diff --git a/packages/neuron/src/pages/download/index.module.scss b/packages/neuron/src/pages/download/index.module.scss index ef177d6..2636054 100644 --- a/packages/neuron/src/pages/download/index.module.scss +++ b/packages/neuron/src/pages/download/index.module.scss @@ -14,6 +14,7 @@ .top { display: flex; justify-content: space-between; + margin-top: 64px; .left { display: flex; @@ -33,6 +34,10 @@ } } } + + @media (max-width: $mobileBreakPoint) { + margin-top: 16px; + } } .assets { diff --git a/packages/neuron/src/pages/download/index.page.tsx b/packages/neuron/src/pages/download/index.page.tsx index e1240fb..3b24146 100644 --- a/packages/neuron/src/pages/download/index.page.tsx +++ b/packages/neuron/src/pages/download/index.page.tsx @@ -29,6 +29,12 @@ const Download: NextPage = ({ release, compatibleData }) => { const assets = useMemo(() => (release ? getAssetsFromNeuronRelease(release) : []), [release]) + const versionComp = ( +
+ {t('Current Version')} {release?.tag_name} +
+ ) + return (
@@ -40,11 +46,7 @@ const Download: NextPage = ({ release, compatibleData }) => {
{t('Download Neuron')}
- {!isMobile && ( -
- {t('Current Version')} {release?.tag_name} -
- )} + {!isMobile && versionComp}
@@ -52,11 +54,7 @@ const Download: NextPage = ({ release, compatibleData }) => {
- {isMobile && ( -
- {t('Current Version')} {release?.tag_name} -
- )} + {isMobile && versionComp} diff --git a/packages/neuron/src/pages/help-center/index.module.scss b/packages/neuron/src/pages/help-center/index.module.scss index 791522c..3ba0083 100644 --- a/packages/neuron/src/pages/help-center/index.module.scss +++ b/packages/neuron/src/pages/help-center/index.module.scss @@ -1,9 +1,13 @@ @use 'sass:math'; +@import '../../styles/variables.module'; .page { + width: 100%; + .top { display: flex; justify-content: space-between; + margin-top: 64px; .left { display: flex; @@ -19,6 +23,10 @@ align-items: flex-end; justify-content: flex-end; } + + @media (max-width: $mobileBreakPoint) { + margin-top: 16px; + } } } @@ -32,11 +40,29 @@ .name { margin-top: 6px; } + + @media (max-width: $mobileBreakPoint) { + font-size: 18px; + + > img { + max-width: 32px; + height: auto; + } + + .name { + margin-top: 4px; + } + } } .text1 { font-weight: 700; font-size: 40px; + + @media (max-width: $mobileBreakPoint) { + font-weight: 600; + font-size: 22px; + } } .search { @@ -68,19 +94,32 @@ .DocSearch-Button-Placeholder { padding: 0 12px; + + @media (max-width: $mobileBreakPoint) { + display: block; + } } } /* stylelint-enable selector-class-pattern */ + + @media (max-width: $mobileBreakPoint) { + margin-top: 16px; + } } .postMenus { display: flex; gap: 32px; justify-content: center; - margin-top: 32px; - margin-bottom: 264px; - padding-top: 24px; + margin: 80px 0 264px; border-top: 1px solid #000; + + @media (max-width: $mobileBreakPoint) { + flex-direction: column; + gap: 24px; + width: 100%; + margin: 24px 0 160px; + } } .postMenu { @@ -111,6 +150,11 @@ color: #00cc9b; } } + + @media (max-width: $mobileBreakPoint) { + height: 48px; + font-size: 16px; + } } .posts { @@ -118,24 +162,29 @@ flex-direction: column; gap: 16px; padding: 24px; + + @media (max-width: $mobileBreakPoint) { + padding: 16px; + } } .post { $lineHeight: 200%; - $fontSize: 16px; $dotSize: 8px; + --fontSize: 16px; + position: relative; padding-left: $dotSize + 8px; overflow: hidden; - font-size: $fontSize; + font-size: var(--fontSize); line-height: $lineHeight; white-space: nowrap; text-overflow: ellipsis; &::before { position: absolute; - top: #{$fontSize * math.div($lineHeight, 200%) - $dotSize / 2}; + top: calc(var(--fontSize) * #{math.div($lineHeight, 200%)} - #{$dotSize / 2}); left: 0; display: block; width: $dotSize; @@ -152,5 +201,9 @@ background: #00cc9b; } } + + @media (max-width: $mobileBreakPoint) { + --fontSize: 14px; + } } } diff --git a/packages/neuron/src/pages/help-center/index.page.tsx b/packages/neuron/src/pages/help-center/index.page.tsx index d568ce2..d0be13c 100644 --- a/packages/neuron/src/pages/help-center/index.page.tsx +++ b/packages/neuron/src/pages/help-center/index.page.tsx @@ -12,6 +12,7 @@ import ImgNeuronLogo from './neuron-logo.png' import ImgHelp from './help.png' import IconMore from './more.svg' import { LinkWithEffect, UpsideDownEffect } from '../../components/UpsideDownEffect' +import { useIsMobile } from '../../hooks' interface PageProps { menusWithPosts: Menu[] @@ -19,6 +20,20 @@ interface PageProps { const HelpCenter: NextPage = ({ menusWithPosts }) => { const { t } = useTranslation('help_center') + const isMobile = useIsMobile() + + const searchComp = ( +
+ {children}} + /> +
+ ) return ( @@ -31,23 +46,16 @@ const HelpCenter: NextPage = ({ menusWithPosts }) => {
{t('Help Center')}
-
- {children}} - /> -
+ {!isMobile && searchComp}
- Help + Help
+ {isMobile && searchComp} +
{menusWithPosts.map(menu => { const firstPostInMenu = menu.posts?.[0] @@ -58,7 +66,12 @@ const HelpCenter: NextPage = ({ menusWithPosts }) => { {firstPostInMenu && ( {(hoverableContainerClass, contentWithEffect) => ( - +
{contentWithEffect}
@@ -70,7 +83,14 @@ const HelpCenter: NextPage = ({ menusWithPosts }) => {
{menu.posts?.map(post => ( - + {post.title} ))} diff --git a/packages/neuron/src/pages/home/github.svg b/packages/neuron/src/pages/home/github.svg index 694f3ad..95bf937 100644 --- a/packages/neuron/src/pages/home/github.svg +++ b/packages/neuron/src/pages/home/github.svg @@ -1,3 +1,3 @@ - + diff --git a/packages/neuron/src/pages/home/index.module.scss b/packages/neuron/src/pages/home/index.module.scss index 78e85cd..4828499 100644 --- a/packages/neuron/src/pages/home/index.module.scss +++ b/packages/neuron/src/pages/home/index.module.scss @@ -84,41 +84,34 @@ } } -.btn { - all: unset; - display: flex; - align-items: center; - justify-content: center; +.btn:not([_='The :not selector to increase specificity']) { + box-sizing: border-box; min-width: 232px; min-height: 72px; font-weight: 600; font-size: 16px; - background: #000; border-radius: 12px; - cursor: pointer; - @media (max-width: $mobileBreakPoint) { - min-width: 224px; - min-height: 56px; - } -} + &.btnDownload { + flex-direction: column; + gap: 6px; + color: #000; -.btnDownload { - flex-direction: column; - gap: 6px; - color: #000; - background: #00c891; + .secondary { + font-weight: 400; + font-size: 14px; + } + } - .secondary { - font-weight: 400; - font-size: 14px; + &.btnGithub { + gap: 8px; + border-width: 1px; } -} -.btnGithub { - gap: 8px; - box-sizing: border-box; - border: 1px solid #f5f5f5; + @media (max-width: $mobileBreakPoint) { + min-width: 224px; + min-height: 56px; + } } .actions { @@ -240,6 +233,7 @@ .text4 { margin-top: 8px; + text-align: center; } .download { diff --git a/packages/neuron/src/pages/home/index.page.tsx b/packages/neuron/src/pages/home/index.page.tsx index df9622c..a427823 100644 --- a/packages/neuron/src/pages/home/index.page.tsx +++ b/packages/neuron/src/pages/home/index.page.tsx @@ -19,6 +19,7 @@ import ImgPrivate from './private.png' import ImgReliable from './reliable.png' import ImgNeuronLogo from './neuron-logo.png' import { ParsedAsset, Release, getAssetsFromNeuronRelease, getLatestRelease } from '../../utils' +import { Button } from '../../components/Button' interface PageProps { locale: string @@ -58,10 +59,10 @@ const Home: NextPage = ({ locale, release }) => { - +
@@ -156,14 +157,14 @@ const DownloadButton: FC> & { release: Relea return ( - + ) } diff --git a/packages/neuron/src/pages/posts/ClassifiedPosts/index.module.scss b/packages/neuron/src/pages/posts/ClassifiedPosts/index.module.scss index 9ba763d..0eacf3b 100644 --- a/packages/neuron/src/pages/posts/ClassifiedPosts/index.module.scss +++ b/packages/neuron/src/pages/posts/ClassifiedPosts/index.module.scss @@ -1,3 +1,5 @@ +$toggleAnimationDuration: 300ms; + .accordionItem { overflow: hidden; } @@ -16,10 +18,10 @@ font-weight: 600; font-size: 16px; line-height: 1; - transition: color 300ms cubic-bezier(0.87, 0, 0.13, 1); + cursor: pointer; .arrow { - transition: transform 300ms cubic-bezier(0.87, 0, 0.13, 1); + transition: transform $toggleAnimationDuration cubic-bezier(0.87, 0, 0.13, 1); } &[data-state='closed'] > .arrow { @@ -31,11 +33,11 @@ min-height: 0; &[data-state='open'] { - animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1); + animation: slideDown $toggleAnimationDuration cubic-bezier(0.87, 0, 0.13, 1); } &[data-state='closed'] { - animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1); + animation: slideUp $toggleAnimationDuration cubic-bezier(0.87, 0, 0.13, 1); } .post { diff --git a/packages/neuron/src/pages/posts/ClassifiedPosts/index.tsx b/packages/neuron/src/pages/posts/ClassifiedPosts/index.tsx index f1365c6..8d32290 100644 --- a/packages/neuron/src/pages/posts/ClassifiedPosts/index.tsx +++ b/packages/neuron/src/pages/posts/ClassifiedPosts/index.tsx @@ -3,10 +3,10 @@ import * as Accordion from '@radix-ui/react-accordion' import clsx from 'clsx' import { useTranslation } from 'react-i18next' import { OverlayScrollbarsComponent } from 'overlayscrollbars-react' +import Link from 'next/link' import { Post, TopLevelMenu, getPostURL } from '../../../utils' import IconArrow from './arrow.svg' import styles from './index.module.scss' -import { LinkWithEffect } from '../../../components/UpsideDownEffect' export const ClassifiedPosts: FC< Omit & { @@ -51,15 +51,14 @@ export const ClassifiedPosts: FC< {menu.posts?.map(post => ( - {post.title} - + ))} diff --git a/packages/neuron/src/pages/posts/HelpDocHeader/index.module.scss b/packages/neuron/src/pages/posts/HelpDocHeader/index.module.scss index 99233b3..683a332 100644 --- a/packages/neuron/src/pages/posts/HelpDocHeader/index.module.scss +++ b/packages/neuron/src/pages/posts/HelpDocHeader/index.module.scss @@ -74,6 +74,10 @@ .colorSchema { margin-right: 24px; cursor: pointer; + + &.placeholder { + visibility: hidden; + } } } diff --git a/packages/neuron/src/pages/posts/HelpDocHeader/index.tsx b/packages/neuron/src/pages/posts/HelpDocHeader/index.tsx index c5abec3..9bca8ed 100644 --- a/packages/neuron/src/pages/posts/HelpDocHeader/index.tsx +++ b/packages/neuron/src/pages/posts/HelpDocHeader/index.tsx @@ -13,7 +13,7 @@ import IconMenu from './menu.svg' import IconClose from './close.svg' import styles from './index.module.scss' import { APPID, Post, SEARCH_KEY, TopLevelMenu, languages, removeURLOrigin } from '../../../utils' -import { useIsMobile } from '../../../hooks' +import { useIsHydrated, useIsMobile } from '../../../hooks' import { ClassifiedPosts } from '../ClassifiedPosts' import { LanguageMenu } from './LanguageMenu' import { LanguageList } from './LanguageList' @@ -29,6 +29,7 @@ export const HelpDocHeader$Desktop: FC = props => { const { t } = useTranslation('posts') const { menuWithPosts, viewingPost, ...divProps } = props const darkMode = useObservableState(appSettings.darkMode$) + const isHydrated = useIsHydrated() return (
@@ -49,7 +50,14 @@ export const HelpDocHeader$Desktop: FC = props => { />
- {darkMode ? ( + {/** + * The DOM structure of these two icon components is the same, so during the hydration phase, + * they will not be re-rendered with the correct latest icons. + * Here, useIsHydrated is used to ensure that the client will always re-render, while the server only renders a fixed placeholder. + */} + {!isHydrated ? ( + + ) : darkMode ? ( appSettings.setDarkMode(false)} /> ) : ( appSettings.setDarkMode(true)} /> diff --git a/packages/neuron/src/pages/posts/Sidebar/index.module.scss b/packages/neuron/src/pages/posts/Sidebar/index.module.scss index 082c844..f1f7501 100644 --- a/packages/neuron/src/pages/posts/Sidebar/index.module.scss +++ b/packages/neuron/src/pages/posts/Sidebar/index.module.scss @@ -42,6 +42,10 @@ $accordionTriggerHeight: 48px; color: #00c891; } + &:hover { + color: #00c891; + } + .arrow { color: var(--colorPrimary); } @@ -49,10 +53,8 @@ $accordionTriggerHeight: 48px; .posts { .post { - display: flex; - align-items: center; - height: 48px; padding: 0 16px; + line-height: 48px; border-right: 2px solid transparent; &[data-selected='true'] { diff --git a/packages/neuron/src/pages/posts/TOC/index.module.scss b/packages/neuron/src/pages/posts/TOC/index.module.scss index 7f2becb..a1697b8 100644 --- a/packages/neuron/src/pages/posts/TOC/index.module.scss +++ b/packages/neuron/src/pages/posts/TOC/index.module.scss @@ -1,13 +1,30 @@ @use 'sass:math'; +$dotSize: 8px; + .toc { + position: relative; margin: 8px 0 24px 20px; - border-left: 1px solid var(--tocBorder); + padding-left: $dotSize / 2; + overflow-y: auto; + + &::before { + position: sticky; + top: 0; + left: 0; + display: block; + float: left; + width: 1px; + height: 100%; + background: var(--tocBorder); + content: ' '; + } } .tocItem { - $lineHeight: 100%; $fontSize: 14px; + $lineHeightPX: 24px; + $lineHeight: 100% * math.div($lineHeightPX, $fontSize); position: relative; margin-top: 28px; @@ -21,14 +38,18 @@ top: #{$fontSize * math.div($lineHeight, 200%)}; left: 0; display: none; - width: 8px; - height: 8px; + width: $dotSize; + height: $dotSize; background-color: #00c891; border-radius: 50%; transform: translate(-50%, -50%); content: ' '; } + &:hover { + color: #00c891; + } + &.active { color: #00c891; diff --git a/packages/neuron/src/pages/posts/[...slug].page.tsx b/packages/neuron/src/pages/posts/[...slug].page.tsx index 62f5e9e..7b2c2f5 100644 --- a/packages/neuron/src/pages/posts/[...slug].page.tsx +++ b/packages/neuron/src/pages/posts/[...slug].page.tsx @@ -6,6 +6,7 @@ import { FC, useMemo } from 'react' import { useObservableState } from 'observable-hooks' import { useTranslation } from 'react-i18next' import { OverlayScrollbarsComponent } from 'overlayscrollbars-react' +import Link from 'next/link' import { TOCContextProvider, TOCItem } from '../../components/TableOfContents' import { Post, @@ -27,7 +28,6 @@ import { TOC } from './TOC' import { appSettings } from '../../services/AppSettings' import { useBodyClass, useFullHeightCSSValue, useIsMobile, useMarkdownProps } from '../../hooks' import { Contacts } from '../../components/Contacts' -import { LinkWithEffect } from '../../components/UpsideDownEffect' interface PageProps { post: Post @@ -49,7 +49,7 @@ const PostPage: NextPage = props => { export const PostPage$Desktop: FC = ({ post, menusWithPosts, menuWithPosts }) => { const { t } = useTranslation('posts') const height = useFullHeightCSSValue() - const mdProps = useMarkdownProps({ imgClass: styles.img }) + const mdProps = useMarkdownProps({ disableLinkEffect: true, imgClass: styles.img, linkClass: styles.link }) const submenuName = menuWithPosts.children?.find(menu => menu.posts?.find(_post => post.key === _post.key))?.name return ( @@ -60,12 +60,12 @@ export const PostPage$Desktop: FC = ({ post, menusWithPosts, menuWith
- {t('Home')} + {t('Home')} {menusWithPosts.map(menu => { const firstPostInMenu = menu.posts?.[0] if (!firstPostInMenu) return null return ( - = ({ post, menusWithPosts, menuWith href={getPostURL(firstPostInMenu)} > {t(menu.name)} - + ) })}
@@ -114,7 +114,7 @@ export const PostPage$Desktop: FC = ({ post, menusWithPosts, menuWith export const PostPage$Mobile: FC = ({ post, menuWithPosts }) => { const { t } = useTranslation('posts') const height = useFullHeightCSSValue() - const mdProps = useMarkdownProps({ imgClass: styles.img }) + const mdProps = useMarkdownProps({ disableLinkEffect: true, imgClass: styles.img, linkClass: styles.link }) const submenuName = menuWithPosts.children?.find(menu => menu.posts?.find(_post => post.key === _post.key))?.name return ( diff --git a/packages/neuron/src/pages/posts/index.module.scss b/packages/neuron/src/pages/posts/index.module.scss index 543f980..610f2a0 100644 --- a/packages/neuron/src/pages/posts/index.module.scss +++ b/packages/neuron/src/pages/posts/index.module.scss @@ -28,19 +28,101 @@ .postContent { padding: 0 24px 16px; + font-size: 14px; .img { max-width: 100%; + margin: 32px 0; + + @media (max-width: $mobileBreakPoint) { + margin: 8px 0; + } + } + + .link { + color: #00c891; } .title { margin: 8px 0 24px; - font-size: 1.25em; + font-size: 20px; @media (max-width: $mobileBreakPoint) { margin: 24px 0; } } + + p, + li { + line-height: 24px; + } + + p, + ul, + ol { + margin: 0; + } + + h2, + h3, + h4 { + margin: 24px 0 16px; + font-weight: 600; + font-size: 16px; + + &:first-child { + margin-top: 0; + } + } + + hr { + margin: 24px 0; + border: 0; + border-bottom: 1px solid #333; + } + + hr + * { + margin-top: 0; + } + + table { + word-break: initial; + + th, + td { + padding: 4px 16px 4px 0; + } + + th { + padding-bottom: 12px; + } + + th:not(:last-child) { + text-align: left; + } + } + + @media (max-width: $mobileBreakPoint) { + p, + li, + h2, + h3, + h4 { + line-height: 32px; + } + + h2, + h3, + h4 { + margin: 32px 0 16px; + font-size: 22px; + } + + // Writing it again here is to address the issue of insufficient specificity caused by the media selector. + hr + * { + margin-top: 0; + } + } } .page { diff --git a/packages/neuron/src/styles/presets.module.scss b/packages/neuron/src/styles/presets.module.scss index 138657e..ca08ec7 100644 --- a/packages/neuron/src/styles/presets.module.scss +++ b/packages/neuron/src/styles/presets.module.scss @@ -1,3 +1,5 @@ +@use 'sass:color'; + @mixin themeLight { --colorPrimary: #333; --colorPrimaryBg: #fff; @@ -13,6 +15,12 @@ --menuShadow1: 0 8px 40px 0 #e5e5e5; --menuBorder1: #e5e5e5; --tocBorder: #e5e5e5; + --btnForeground: #fff; + --btnForegroundDisabled: #{rgba(#fff, 0.5)}; + --btnBackground: #00c891; + --btnBackgroundDisabled: #{color.mix(#fff, #00c891, $weight: 50%)}; + --btnBackgroundHover: #{color.mix(#000, #00c891, $weight: 20%)}; + --btnBackgroundActive: #{color.mix(#000, #00c891, $weight: 40%)}; } .themeLight { @@ -34,6 +42,12 @@ --menuShadow1: unset; --menuBorder1: rgb(255 255 255 / 20%); --tocBorder: #343e3c; + --btnForeground: #333; + --btnForegroundDisabled: #{rgba(#333, 0.5)}; + --btnBackground: #00c891; + --btnBackgroundDisabled: #{color.mix(#000, #00c891, $weight: 50%)}; + --btnBackgroundHover: #{color.mix(#000, #00c891, $weight: 20%)}; + --btnBackgroundActive: #{color.mix(#000, #00c891, $weight: 40%)}; } .themeDark {