mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
refactor(console): add missing section title and adjust card layout in get-started page (#4428)
This commit is contained in:
parent
5e8b1bd598
commit
54321d93a2
3 changed files with 67 additions and 28 deletions
27
packages/console/src/hooks/use-window-resize.ts
Normal file
27
packages/console/src/hooks/use-window-resize.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { useEffect, useLayoutEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
type Callback = (event?: UIEvent) => void;
|
||||||
|
|
||||||
|
const useWindowResize = (callback: Callback) => {
|
||||||
|
const callbackRef = useRef<Callback>(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;
|
|
@ -1,4 +1,5 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { type Ref, forwardRef } from 'react';
|
||||||
|
|
||||||
import { type Guide } from '@/assets/docs/guides/types';
|
import { type Guide } from '@/assets/docs/guides/types';
|
||||||
|
|
||||||
|
@ -15,20 +16,16 @@ type GuideGroupProps = {
|
||||||
onClickGuide: (data: SelectedGuide) => void;
|
onClickGuide: (data: SelectedGuide) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function GuideGroup({
|
function GuideGroup(
|
||||||
className,
|
{ className, categoryName, guides, hasCardBorder, isCompact, onClickGuide }: GuideGroupProps,
|
||||||
categoryName,
|
ref: Ref<HTMLDivElement>
|
||||||
guides,
|
) {
|
||||||
hasCardBorder,
|
|
||||||
isCompact,
|
|
||||||
onClickGuide,
|
|
||||||
}: GuideGroupProps) {
|
|
||||||
if (!guides?.length) {
|
if (!guides?.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(styles.guideGroup, className)}>
|
<div ref={ref} className={classNames(styles.guideGroup, className)}>
|
||||||
{categoryName && <label>{categoryName}</label>}
|
{categoryName && <label>{categoryName}</label>}
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{guides.map((guide) => (
|
{guides.map((guide) => (
|
||||||
|
@ -45,4 +42,4 @@ function GuideGroup({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GuideGroup;
|
export default forwardRef<HTMLDivElement, GuideGroupProps>(GuideGroup);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { withAppInsights } from '@logto/app-insights/react';
|
import { withAppInsights } from '@logto/app-insights/react';
|
||||||
import { Theme, type Application } from '@logto/schemas';
|
import { Theme, type Application } from '@logto/schemas';
|
||||||
import classNames from 'classnames';
|
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 { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import CheckPreviewDark from '@/assets/icons/check-demo-dark.svg';
|
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 TextLink from '@/ds-components/TextLink';
|
||||||
import useTenantPathname from '@/hooks/use-tenant-pathname';
|
import useTenantPathname from '@/hooks/use-tenant-pathname';
|
||||||
import useTheme from '@/hooks/use-theme';
|
import useTheme from '@/hooks/use-theme';
|
||||||
|
import useWindowResize from '@/hooks/use-window-resize';
|
||||||
|
|
||||||
import CreateForm from '../Applications/components/CreateForm';
|
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 useAppGuideMetadata from '../Applications/components/GuideLibrary/hook';
|
||||||
|
|
||||||
import FreePlanNotification from './FreePlanNotification';
|
import FreePlanNotification from './FreePlanNotification';
|
||||||
|
@ -39,16 +41,26 @@ function GetStarted() {
|
||||||
const [selectedGuide, setSelectedGuide] = useState<SelectedGuide>();
|
const [selectedGuide, setSelectedGuide] = useState<SelectedGuide>();
|
||||||
const [_, getStructuredMetadata] = useAppGuideMetadata();
|
const [_, getStructuredMetadata] = useAppGuideMetadata();
|
||||||
const [showCreateForm, setShowCreateForm] = useState<boolean>(false);
|
const [showCreateForm, setShowCreateForm] = useState<boolean>(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<HTMLDivElement>(null);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { PreviewIcon, SocialIcon, RbacIcon } = icons[theme];
|
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
|
* Slice the guide metadata as we only need to show 1 row of guide cards in get-started page
|
||||||
* a few most popular SDK guides, and 4 makes it easy to have a 4 x 1 or 2 x 2 card layout.
|
|
||||||
*/
|
*/
|
||||||
const featuredAppGuides = useMemo(
|
const featuredAppGuides = useMemo(
|
||||||
() => getStructuredMetadata().featured.slice(0, 4),
|
() => getStructuredMetadata().featured.slice(0, visibleCardCount),
|
||||||
[getStructuredMetadata]
|
[visibleCardCount, getStructuredMetadata]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onClickGuide = useCallback((data: SelectedGuide) => {
|
const onClickGuide = useCallback((data: SelectedGuide) => {
|
||||||
|
@ -78,21 +90,23 @@ function GetStarted() {
|
||||||
<FreePlanNotification />
|
<FreePlanNotification />
|
||||||
<Card className={styles.card}>
|
<Card className={styles.card}>
|
||||||
<div className={styles.title}>{t('get_started.develop.title')}</div>
|
<div className={styles.title}>{t('get_started.develop.title')}</div>
|
||||||
<div className={styles.grid}>
|
<GuideGroup
|
||||||
{featuredAppGuides.map((guide) => (
|
ref={containerRef}
|
||||||
<GuideCard key={guide.id} hasBorder data={guide} onClick={onClickGuide} />
|
hasCardBorder
|
||||||
))}
|
guides={featuredAppGuides}
|
||||||
{selectedGuide?.target !== 'API' && showCreateForm && (
|
onClickGuide={onClickGuide}
|
||||||
<CreateForm
|
/>
|
||||||
defaultCreateType={selectedGuide?.target}
|
{selectedGuide?.target !== 'API' && showCreateForm && (
|
||||||
defaultCreateFrameworkName={selectedGuide?.name}
|
<CreateForm
|
||||||
onClose={onCloseCreateForm}
|
defaultCreateType={selectedGuide?.target}
|
||||||
/>
|
defaultCreateFrameworkName={selectedGuide?.name}
|
||||||
)}
|
onClose={onCloseCreateForm}
|
||||||
</div>
|
/>
|
||||||
|
)}
|
||||||
<TextLink to="/applications/create">{t('get_started.view_all')}</TextLink>
|
<TextLink to="/applications/create">{t('get_started.view_all')}</TextLink>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className={styles.card}>
|
<Card className={styles.card}>
|
||||||
|
<div className={styles.title}>{t('get_started.customize.title')}</div>
|
||||||
<div className={styles.borderBox}>
|
<div className={styles.borderBox}>
|
||||||
<div className={styles.rowWrapper}>
|
<div className={styles.rowWrapper}>
|
||||||
<div className={styles.icon}>
|
<div className={styles.icon}>
|
||||||
|
@ -134,6 +148,7 @@ function GetStarted() {
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card className={styles.card}>
|
<Card className={styles.card}>
|
||||||
|
<div className={styles.title}>{t('get_started.manage.title')}</div>
|
||||||
<div className={styles.borderBox}>
|
<div className={styles.borderBox}>
|
||||||
<div className={styles.rowWrapper}>
|
<div className={styles.rowWrapper}>
|
||||||
<div className={styles.icon}>
|
<div className={styles.icon}>
|
||||||
|
|
Loading…
Reference in a new issue