From f1ded1168bf66b795629a0a1c13e0bb7f8027a72 Mon Sep 17 00:00:00 2001 From: Charles Zhao Date: Wed, 6 Sep 2023 22:55:11 +0800 Subject: [PATCH] refactor: improve guide responsive rules (#4436) --- .../assets/docs/guides/spa-vanilla/README.mdx | 1 - .../mdx-components/Steps/index.module.scss | 32 +++-- .../src/mdx-components/Steps/index.tsx | 49 +++---- .../mdx-components/UriInputField/index.tsx | 54 ++------ .../components/GuideDrawer/index.module.scss | 2 +- .../components/GuideDrawer/index.tsx | 1 - .../src/pages/ApplicationDetails/index.tsx | 2 +- .../components/Guide/index.module.scss | 82 +++++------- .../Applications/components/Guide/index.tsx | 35 +++-- .../components/GuideCard/index.module.scss | 5 +- .../components/GuideCard/index.tsx | 36 +++-- .../components/GuideGroup/index.tsx | 6 +- .../components/GuideHeader/index.module.scss | 9 +- .../components/GuideHeader/index.tsx | 75 ++++------- .../components/GuideLibrary/index.module.scss | 48 ++++++- .../components/GuideLibrary/index.tsx | 126 +++++++++--------- .../GuideLibraryModal/index.module.scss | 36 +++-- .../components/GuideLibraryModal/index.tsx | 23 ++-- .../components/GuideModal/index.module.scss | 19 +++ .../GuideModal.tsx => GuideModal/index.tsx} | 10 +- .../console/src/pages/Applications/index.tsx | 2 +- packages/console/src/scss/_dimensions.scss | 8 ++ 22 files changed, 346 insertions(+), 315 deletions(-) create mode 100644 packages/console/src/pages/Applications/components/GuideModal/index.module.scss rename packages/console/src/pages/Applications/components/{Guide/GuideModal.tsx => GuideModal/index.tsx} (65%) diff --git a/packages/console/src/assets/docs/guides/spa-vanilla/README.mdx b/packages/console/src/assets/docs/guides/spa-vanilla/README.mdx index 450ad45d5..8173355e4 100644 --- a/packages/console/src/assets/docs/guides/spa-vanilla/README.mdx +++ b/packages/console/src/assets/docs/guides/spa-vanilla/README.mdx @@ -115,7 +115,6 @@ After signing out, it'll be great to redirect user back to your website. Let's a diff --git a/packages/console/src/mdx-components/Steps/index.module.scss b/packages/console/src/mdx-components/Steps/index.module.scss index 1a02abfc5..4ee3bfd53 100644 --- a/packages/console/src/mdx-components/Steps/index.module.scss +++ b/packages/console/src/mdx-components/Steps/index.module.scss @@ -1,24 +1,20 @@ @use '@/scss/underscore' as _; +@use '@/scss/dimensions' as dim; .wrapper { - position: relative; -} - -.fullWidth { width: 100%; } .navigationAnchor { position: absolute; - inset: 0 auto 0 0; - transform: translateX(-100%); + inset: _.unit(6) auto _.unit(6) _.unit(6); } .navigation { position: sticky; - top: 0; + top: _.unit(6); flex-shrink: 0; - margin-right: _.unit(4); + margin-right: _.unit(7.5); width: 220px; > :not(:last-child) { @@ -27,11 +23,21 @@ } .content { - max-width: 858px; + width: 100%; + min-width: dim.$guide-content-min-width; + max-width: dim.$guide-content-max-width; + padding: dim.$guide-content-padding calc(dim.$guide-sidebar-width + dim.$guide-panel-gap + dim.$guide-content-padding); + margin: 0 auto; + position: relative; > :not(:last-child) { margin-bottom: _.unit(6); } + + &.compact { + min-width: 652px; + padding: 0; + } } .stepper { @@ -51,3 +57,11 @@ background: var(--color-focused-variant); } } + +@media screen and (max-width: dim.$guide-content-max-width) { + .content { + margin: 0; + padding-right: dim.$guide-content-padding; + max-width: calc(dim.$guide-main-content-max-width + dim.$guide-sidebar-width + dim.$guide-panel-gap + 2 * dim.$guide-content-padding); + } +} diff --git a/packages/console/src/mdx-components/Steps/index.tsx b/packages/console/src/mdx-components/Steps/index.tsx index adca53fb3..02f40c354 100644 --- a/packages/console/src/mdx-components/Steps/index.tsx +++ b/packages/console/src/mdx-components/Steps/index.tsx @@ -48,6 +48,7 @@ export default function Steps({ children: reactChildren }: Props) { : [reactChildren, furtherReadings], [furtherReadings, reactChildren] ); + const { isCompact } = useContext(GuideContext); useEffect(() => { @@ -85,30 +86,30 @@ export default function Steps({ children: reactChildren }: Props) { }; return ( -
- {!isCompact && ( -
- -
- )} -
+
+
+ {!isCompact && ( +
+ +
+ )} {children.map((component, index) => React.cloneElement(component, { diff --git a/packages/console/src/mdx-components/UriInputField/index.tsx b/packages/console/src/mdx-components/UriInputField/index.tsx index 057f1fbe2..4afde81b5 100644 --- a/packages/console/src/mdx-components/UriInputField/index.tsx +++ b/packages/console/src/mdx-components/UriInputField/index.tsx @@ -10,12 +10,10 @@ import useSWR from 'swr'; import MultiTextInputField from '@/components/MultiTextInputField'; import Button from '@/ds-components/Button'; -import FormField from '@/ds-components/FormField'; import { convertRhfErrorMessage, createValidatorForRhf, } from '@/ds-components/MultiTextInput/utils'; -import TextInput from '@/ds-components/TextInput'; import type { RequestError } from '@/hooks/use-api'; import useApi from '@/hooks/use-api'; import { GuideContext } from '@/pages/Applications/components/Guide'; @@ -42,9 +40,7 @@ function UriInputField({ name, defaultValue }: Props) { } = methods; const { app: { id: appId }, - isCompact, } = useContext(GuideContext); - const isSingle = !isCompact; const { data, mutate } = useSWR(`api/applications/${appId}`); const ref = useRef(null); @@ -93,10 +89,7 @@ function UriInputField({ name, defaultValue }: Props) { defaultValue={defaultValueArray} rules={{ validate: createValidatorForRhf({ - required: t( - isSingle ? 'errors.required_field_missing' : 'errors.required_field_missing_plural', - { field: title } - ), + required: t('errors.required_field_missing_plural', { field: title }), pattern: { verify: (value) => !value || uriValidator(value), message: t('errors.invalid_uri_format'), @@ -108,39 +101,18 @@ function UriInputField({ name, defaultValue }: Props) { return (
- {isSingle && ( - - { - onChange([value]); - }} - onKeyPress={(event) => { - onKeyPress(event, value); - }} - /> - - )} - {!isSingle && ( - { - onKeyPress(event, value); - }} - /> - )} + { + onKeyPress(event, value); + }} + />
- - )} -
-
+ + {memorizedContext && ( + + )} + ); } diff --git a/packages/console/src/pages/Applications/components/GuideCard/index.module.scss b/packages/console/src/pages/Applications/components/GuideCard/index.module.scss index 9495c42b8..7f829d685 100644 --- a/packages/console/src/pages/Applications/components/GuideCard/index.module.scss +++ b/packages/console/src/pages/Applications/components/GuideCard/index.module.scss @@ -11,9 +11,10 @@ min-width: 220px; max-width: 460px; justify-content: space-between; + cursor: pointer; - &.compact { - cursor: pointer; + &.hasButton { + cursor: default; } &.hasBorder { diff --git a/packages/console/src/pages/Applications/components/GuideCard/index.tsx b/packages/console/src/pages/Applications/components/GuideCard/index.tsx index c23919f1d..f98a8f718 100644 --- a/packages/console/src/pages/Applications/components/GuideCard/index.tsx +++ b/packages/console/src/pages/Applications/components/GuideCard/index.tsx @@ -1,6 +1,6 @@ import { ApplicationType } from '@logto/schemas'; import classNames from 'classnames'; -import { Suspense, useContext } from 'react'; +import { Suspense, useCallback, useContext } from 'react'; import { type Guide, type GuideMetadata } from '@/assets/docs/guides/types'; import ProTag from '@/components/ProTag'; @@ -24,10 +24,10 @@ type Props = { data: Guide; onClick: (data: SelectedGuide) => void; hasBorder?: boolean; - isCompact?: boolean; + hasButton?: boolean; }; -function GuideCard({ data, onClick, hasBorder, isCompact }: Props) { +function GuideCard({ data, onClick, hasBorder, hasButton }: Props) { const { navigate } = useTenantPathname(); const { currentTenantId } = useContext(TenantsContext); const { data: currentPlan } = useSubscriptionPlan(currentTenantId); @@ -41,26 +41,26 @@ function GuideCard({ data, onClick, hasBorder, isCompact }: Props) { metadata: { target, name, description }, } = data; - const onClickCard = () => { - if (!isCompact) { - return; + const handleClick = useCallback(() => { + if (isSubscriptionRequired) { + navigate(subscriptionPage); + } else { + onClick({ id, target, name }); } - - onClick({ id, target, name }); - }; + }, [id, isSubscriptionRequired, name, target, navigate, onClick]); return (
@@ -77,19 +77,13 @@ function GuideCard({ data, onClick, hasBorder, isCompact }: Props) {
- {!isCompact && ( + {hasButton && (
diff --git a/packages/console/src/pages/Applications/components/GuideGroup/index.tsx b/packages/console/src/pages/Applications/components/GuideGroup/index.tsx index d72138a7c..903c13deb 100644 --- a/packages/console/src/pages/Applications/components/GuideGroup/index.tsx +++ b/packages/console/src/pages/Applications/components/GuideGroup/index.tsx @@ -12,12 +12,12 @@ type GuideGroupProps = { categoryName?: string; guides?: readonly Guide[]; hasCardBorder?: boolean; - isCompact?: boolean; + hasCardButton?: boolean; onClickGuide: (data: SelectedGuide) => void; }; function GuideGroup( - { className, categoryName, guides, hasCardBorder, isCompact, onClickGuide }: GuideGroupProps, + { className, categoryName, guides, hasCardBorder, hasCardButton, onClickGuide }: GuideGroupProps, ref: Ref ) { if (!guides?.length) { @@ -31,8 +31,8 @@ function GuideGroup( {guides.map((guide) => ( diff --git a/packages/console/src/pages/Applications/components/GuideHeader/index.module.scss b/packages/console/src/pages/Applications/components/GuideHeader/index.module.scss index 527eda8f6..3535d2a84 100644 --- a/packages/console/src/pages/Applications/components/GuideHeader/index.module.scss +++ b/packages/console/src/pages/Applications/components/GuideHeader/index.module.scss @@ -4,6 +4,7 @@ display: flex; align-items: center; background-color: var(--color-base); + width: 100%; height: 64px; padding: 0 _.unit(6); flex-shrink: 0; @@ -33,6 +34,12 @@ } .requestSdkButton { - margin-right: _.unit(2); + margin-right: _.unit(15); + } +} + +@media screen and (max-width: 918px) { + .header .requestSdkButton { + margin-right: 0; } } diff --git a/packages/console/src/pages/Applications/components/GuideHeader/index.tsx b/packages/console/src/pages/Applications/components/GuideHeader/index.tsx index 3ba4fbdd7..0ff210ade 100644 --- a/packages/console/src/pages/Applications/components/GuideHeader/index.tsx +++ b/packages/console/src/pages/Applications/components/GuideHeader/index.tsx @@ -1,5 +1,4 @@ import { useCallback, useState } from 'react'; -import { useTranslation } from 'react-i18next'; import Box from '@/assets/icons/box.svg'; import Close from '@/assets/icons/close.svg'; @@ -9,19 +8,15 @@ import Button from '@/ds-components/Button'; import CardTitle from '@/ds-components/CardTitle'; import IconButton from '@/ds-components/IconButton'; import Spacer from '@/ds-components/Spacer'; -import Tooltip from '@/ds-components/Tip/Tooltip'; import RequestGuide from './RequestGuide'; import * as styles from './index.module.scss'; type Props = { - isCompact?: boolean; onClose: () => void; }; -function GuideHeader({ isCompact = false, onClose }: Props) { - const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - +function GuideHeader({ onClose }: Props) { const [isRequestGuideOpen, setIsRequestGuideOpen] = useState(false); const onRequestGuideClose = useCallback(() => { setIsRequestGuideOpen(false); @@ -29,51 +24,29 @@ function GuideHeader({ isCompact = false, onClose }: Props) { return (
- {isCompact && ( - <> - - - - - - - - )} - {!isCompact && ( - <> - - - -
- - -
); diff --git a/packages/console/src/pages/Applications/components/GuideLibrary/index.module.scss b/packages/console/src/pages/Applications/components/GuideLibrary/index.module.scss index 01f7bcd1a..d68e5eb25 100644 --- a/packages/console/src/pages/Applications/components/GuideLibrary/index.module.scss +++ b/packages/console/src/pages/Applications/components/GuideLibrary/index.module.scss @@ -1,17 +1,36 @@ @use '@/scss/underscore' as _; +@use '@/scss/dimensions' as dim; .container { - display: flex; - gap: _.unit(7); + width: 100%; +} + +.wrapper { + width: 100%; + min-width: dim.$guide-content-min-width; + max-width: dim.$guide-content-max-width; + margin: 0 auto; + position: relative; + + &.hasFilters { + padding: dim.$guide-content-padding calc(dim.$guide-sidebar-width + dim.$guide-panel-gap + dim.$guide-content-padding); + } +} + +.filterAnchor { + position: absolute; + top: 0; + right: 100%; } .filters { + position: sticky; + top: 0; display: flex; flex-direction: column; + width: dim.$guide-sidebar-width; gap: _.unit(4); - padding: _.unit(8) 0 _.unit(8) _.unit(11); - flex-shrink: 0; - overflow-y: auto; + margin-right: dim.$guide-panel-gap; label { font: var(--font-label-2); @@ -45,16 +64,23 @@ display: flex; flex-direction: column; padding-bottom: _.unit(8); - overflow-y: auto; + position: relative; > div { flex: unset; } } +.wrapper.hasFilters .groups { + max-width: dim.$guide-main-content-max-width; +} + .guideGroup { flex: 1; - margin: _.unit(8) _.unit(8) 0 0; + + + .guideGroup { + margin-top: _.unit(8); + } } .emptyPlaceholder { @@ -63,3 +89,11 @@ width: 100%; height: 70%; } + +@media screen and (max-width: dim.$guide-content-max-width) { + .wrapper.hasFilters { + margin-left: 0; + padding-right: dim.$guide-content-padding; + max-width: calc(dim.$guide-main-content-max-width + dim.$guide-sidebar-width + dim.$guide-panel-gap + 2 * dim.$guide-content-padding); + } +} diff --git a/packages/console/src/pages/Applications/components/GuideLibrary/index.tsx b/packages/console/src/pages/Applications/components/GuideLibrary/index.tsx index eaaef54b8..ca0e125bc 100644 --- a/packages/console/src/pages/Applications/components/GuideLibrary/index.tsx +++ b/packages/console/src/pages/Applications/components/GuideLibrary/index.tsx @@ -25,10 +25,11 @@ import * as styles from './index.module.scss'; type Props = { className?: string; hasCardBorder?: boolean; + hasCardButton?: boolean; hasFilters?: boolean; }; -function GuideLibrary({ className, hasCardBorder, hasFilters }: Props) { +function GuideLibrary({ className, hasCardBorder, hasCardButton, hasFilters }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.applications.guide' }); const { navigate } = useTenantPathname(); const [keyword, setKeyword] = useState(''); @@ -69,66 +70,71 @@ function GuideLibrary({ className, hasCardBorder, hasFilters }: Props) { ); return ( -
- {hasFilters && ( -
- - } - placeholder={t('filter.placeholder')} - value={keyword} - onChange={(event) => { - setKeyword(event.currentTarget.value); - }} - /> -
- ({ - title: `applications.guide.categories.${category}`, - value: category, - }))} - value={filterCategories} - onChange={(value) => { - const sortedValue = allAppGuideCategories.filter((category) => - value.includes(category) - ); - setFilterCategories(sortedValue); - }} - /> - {isM2mDisabledForCurrentPlan && } -
-
- )} - {keyword && - (filteredMetadata?.length ? ( - - ) : ( - - ))} - {!keyword && ( - - {(filterCategories.length > 0 ? filterCategories : allAppGuideCategories).map( - (category) => - structuredMetadata[category].length > 0 && ( - +
+
+ {hasFilters && ( +
+
+ + } + placeholder={t('filter.placeholder')} + value={keyword} + onChange={(event) => { + setKeyword(event.currentTarget.value); + }} /> - ) +
+ ({ + title: `applications.guide.categories.${category}`, + value: category, + }))} + value={filterCategories} + onChange={(value) => { + const sortedValue = allAppGuideCategories.filter((category) => + value.includes(category) + ); + setFilterCategories(sortedValue); + }} + /> + {isM2mDisabledForCurrentPlan && } +
+
+
)} - - )} + {keyword && + (filteredMetadata?.length ? ( + + ) : ( + + ))} + {!keyword && + (filterCategories.length > 0 ? filterCategories : allAppGuideCategories).map( + (category) => + structuredMetadata[category].length > 0 && ( + + ) + )} +
+
{selectedGuide?.target !== 'API' && showCreateForm && ( )} -
+ ); } diff --git a/packages/console/src/pages/Applications/components/GuideLibraryModal/index.module.scss b/packages/console/src/pages/Applications/components/GuideLibraryModal/index.module.scss index 20e35b541..df8306352 100644 --- a/packages/console/src/pages/Applications/components/GuideLibraryModal/index.module.scss +++ b/packages/console/src/pages/Applications/components/GuideLibraryModal/index.module.scss @@ -1,38 +1,50 @@ @use '@/scss/underscore' as _; +@use '@/scss/dimensions' as dim; .container { display: flex; flex-direction: column; background-color: var(--color-base); + width: 100vw; height: 100vh; + overflow-x: auto; + + > * { + min-width: dim.$guide-content-min-width; + } .content { flex: 1; - display: flex; - width: 100%; overflow: hidden; } .actionBar { - display: flex; - align-items: center; inset: auto 0 0 0; - padding: _.unit(4) _.unit(8); + width: 100%; + padding: _.unit(4) _.unit(6); background-color: var(--color-layer-1); box-shadow: var(--shadow-3); z-index: 1; + .wrapper { + display: flex; + align-items: center; + justify-content: space-between; + max-width: dim.$guide-main-content-max-width; + margin: 0 auto; + } + .text { font: var(--font-body-2); color: var(--color-text); - margin-left: _.unit(62.5); - margin-right: _.unit(4); + margin-right: _.unit(3); @include _.multi-line-ellipsis(2); } - - .button { - margin-right: 0; - margin-left: auto; - } + } +} + +@media screen and (max-width: dim.$guide-content-max-width) { + .container .actionBar .wrapper { + margin: 0 0 0 _.unit(62.5); } } diff --git a/packages/console/src/pages/Applications/components/GuideLibraryModal/index.tsx b/packages/console/src/pages/Applications/components/GuideLibraryModal/index.tsx index 6dffef933..4d82f8e40 100644 --- a/packages/console/src/pages/Applications/components/GuideLibraryModal/index.tsx +++ b/packages/console/src/pages/Applications/components/GuideLibraryModal/index.tsx @@ -30,18 +30,19 @@ function GuideLibraryModal({ isOpen, onClose }: Props) { >
- +
{showCreateForm && ( diff --git a/packages/console/src/pages/Applications/components/GuideModal/index.module.scss b/packages/console/src/pages/Applications/components/GuideModal/index.module.scss new file mode 100644 index 000000000..8fae7bd24 --- /dev/null +++ b/packages/console/src/pages/Applications/components/GuideModal/index.module.scss @@ -0,0 +1,19 @@ +@use '@/scss/underscore' as _; +@use '@/scss/dimensions' as dim; + +.modalContainer { + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + background-color: var(--color-base); + overflow-x: auto; + + > * { + min-width: dim.$guide-content-min-width; + } +} + +.guide { + flex: 1; +} diff --git a/packages/console/src/pages/Applications/components/Guide/GuideModal.tsx b/packages/console/src/pages/Applications/components/GuideModal/index.tsx similarity index 65% rename from packages/console/src/pages/Applications/components/Guide/GuideModal.tsx rename to packages/console/src/pages/Applications/components/GuideModal/index.tsx index 6e964bb27..9c06066f2 100644 --- a/packages/console/src/pages/Applications/components/Guide/GuideModal.tsx +++ b/packages/console/src/pages/Applications/components/GuideModal/index.tsx @@ -3,7 +3,10 @@ import Modal from 'react-modal'; import * as modalStyles from '@/scss/modal.module.scss'; -import Guide from '.'; +import Guide from '../Guide'; +import GuideHeader from '../GuideHeader'; + +import * as styles from './index.module.scss'; type Props = { guideId: string; @@ -27,7 +30,10 @@ function GuideModal({ guideId, app, onClose }: Props) { className={modalStyles.fullScreen} onRequestClose={closeModal} > - +
+ + +
); } diff --git a/packages/console/src/pages/Applications/index.tsx b/packages/console/src/pages/Applications/index.tsx index 252794af2..d7632e284 100644 --- a/packages/console/src/pages/Applications/index.tsx +++ b/packages/console/src/pages/Applications/index.tsx @@ -85,7 +85,7 @@ function Applications() { title="applications.guide.header_title" subtitle="applications.guide.header_subtitle" /> - + )} {(isLoading || !!applications?.length) && ( diff --git a/packages/console/src/scss/_dimensions.scss b/packages/console/src/scss/_dimensions.scss index c4e473c34..78a92386c 100644 --- a/packages/console/src/scss/_dimensions.scss +++ b/packages/console/src/scss/_dimensions.scss @@ -6,3 +6,11 @@ $modal-layout-grid-large: 850px; $modal-layout-grid-medium: 668px; $modal-layout-grid-small: 500px; $form-text-field-width: 556px; + +// Guide related dimensions +$guide-main-content-max-width: 858px; +$guide-sidebar-width: 220px; +$guide-panel-gap: 30px; +$guide-content-padding: 24px; +$guide-content-max-width: calc($guide-main-content-max-width + 2 * ($guide-sidebar-width + $guide-panel-gap + $guide-content-padding)); +$guide-content-min-width: 750px;