From 54321d93a207aa76bf0db7015ef6d0559a77c81f Mon Sep 17 00:00:00 2001 From: Charles Zhao Date: Wed, 6 Sep 2023 10:10:35 +0800 Subject: [PATCH] refactor(console): add missing section title and adjust card layout in get-started page (#4428) --- .../console/src/hooks/use-window-resize.ts | 27 ++++++++++ .../components/GuideGroup/index.tsx | 17 +++---- .../console/src/pages/GetStarted/index.tsx | 51 ++++++++++++------- 3 files changed, 67 insertions(+), 28 deletions(-) create mode 100644 packages/console/src/hooks/use-window-resize.ts diff --git a/packages/console/src/hooks/use-window-resize.ts b/packages/console/src/hooks/use-window-resize.ts new file mode 100644 index 000000000..c4160667a --- /dev/null +++ b/packages/console/src/hooks/use-window-resize.ts @@ -0,0 +1,27 @@ +import { useEffect, useLayoutEffect, useRef } from 'react'; + +type Callback = (event?: UIEvent) => void; + +const useWindowResize = (callback: Callback) => { + const callbackRef = useRef(callback); + + useEffect(() => { + // eslint-disable-next-line @silverhand/fp/no-mutation + callbackRef.current = callback; + }, [callback]); + + useLayoutEffect(() => { + const handler: Callback = (event) => { + callbackRef.current(event); + }; + + handler(); + window.addEventListener('resize', handler); + + return () => { + window.removeEventListener('resize', handler); + }; + }, []); +}; + +export default useWindowResize; diff --git a/packages/console/src/pages/Applications/components/GuideGroup/index.tsx b/packages/console/src/pages/Applications/components/GuideGroup/index.tsx index 0888da16d..d72138a7c 100644 --- a/packages/console/src/pages/Applications/components/GuideGroup/index.tsx +++ b/packages/console/src/pages/Applications/components/GuideGroup/index.tsx @@ -1,4 +1,5 @@ import classNames from 'classnames'; +import { type Ref, forwardRef } from 'react'; import { type Guide } from '@/assets/docs/guides/types'; @@ -15,20 +16,16 @@ type GuideGroupProps = { onClickGuide: (data: SelectedGuide) => void; }; -function GuideGroup({ - className, - categoryName, - guides, - hasCardBorder, - isCompact, - onClickGuide, -}: GuideGroupProps) { +function GuideGroup( + { className, categoryName, guides, hasCardBorder, isCompact, onClickGuide }: GuideGroupProps, + ref: Ref +) { if (!guides?.length) { return null; } return ( -
+
{categoryName && }
{guides.map((guide) => ( @@ -45,4 +42,4 @@ function GuideGroup({ ); } -export default GuideGroup; +export default forwardRef(GuideGroup); diff --git a/packages/console/src/pages/GetStarted/index.tsx b/packages/console/src/pages/GetStarted/index.tsx index 752694db8..6dce2a2f5 100644 --- a/packages/console/src/pages/GetStarted/index.tsx +++ b/packages/console/src/pages/GetStarted/index.tsx @@ -1,7 +1,7 @@ import { withAppInsights } from '@logto/app-insights/react'; import { Theme, type Application } from '@logto/schemas'; import classNames from 'classnames'; -import { useCallback, useContext, useMemo, useState } from 'react'; +import { useCallback, useContext, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import CheckPreviewDark from '@/assets/icons/check-demo-dark.svg'; @@ -19,9 +19,11 @@ import Spacer from '@/ds-components/Spacer'; import TextLink from '@/ds-components/TextLink'; import useTenantPathname from '@/hooks/use-tenant-pathname'; import useTheme from '@/hooks/use-theme'; +import useWindowResize from '@/hooks/use-window-resize'; import CreateForm from '../Applications/components/CreateForm'; -import GuideCard, { type SelectedGuide } from '../Applications/components/GuideCard'; +import { type SelectedGuide } from '../Applications/components/GuideCard'; +import GuideGroup from '../Applications/components/GuideGroup'; import useAppGuideMetadata from '../Applications/components/GuideLibrary/hook'; import FreePlanNotification from './FreePlanNotification'; @@ -39,16 +41,26 @@ function GetStarted() { const [selectedGuide, setSelectedGuide] = useState(); const [_, getStructuredMetadata] = useAppGuideMetadata(); const [showCreateForm, setShowCreateForm] = useState(false); + // The number of visible guide cards to show in one row per the current screen width + const [visibleCardCount, setVisibleCardCount] = useState(4); + const containerRef = useRef(null); const theme = useTheme(); const { PreviewIcon, SocialIcon, RbacIcon } = icons[theme]; + useWindowResize(() => { + const containerWidth = containerRef.current?.clientWidth ?? 0; + + // Responsive breakpoints (1080, 680px) are defined in `GuideGroup` component SCSS, + // and we need to keep them consistent. + setVisibleCardCount(containerWidth > 1080 ? 4 : containerWidth > 680 ? 3 : 2); + }); + /** - * Only need 4 featured guides at most, since by design in get-started page we need to show - * a few most popular SDK guides, and 4 makes it easy to have a 4 x 1 or 2 x 2 card layout. + * Slice the guide metadata as we only need to show 1 row of guide cards in get-started page */ const featuredAppGuides = useMemo( - () => getStructuredMetadata().featured.slice(0, 4), - [getStructuredMetadata] + () => getStructuredMetadata().featured.slice(0, visibleCardCount), + [visibleCardCount, getStructuredMetadata] ); const onClickGuide = useCallback((data: SelectedGuide) => { @@ -78,21 +90,23 @@ function GetStarted() {
{t('get_started.develop.title')}
-
- {featuredAppGuides.map((guide) => ( - - ))} - {selectedGuide?.target !== 'API' && showCreateForm && ( - - )} -
+ + {selectedGuide?.target !== 'API' && showCreateForm && ( + + )} {t('get_started.view_all')}
+
{t('get_started.customize.title')}
@@ -134,6 +148,7 @@ function GetStarted() {
+
{t('get_started.manage.title')}