0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-07 23:01:25 -05:00

refactor: improve guide responsive rules ()

This commit is contained in:
Charles Zhao 2023-09-06 22:55:11 +08:00 committed by GitHub
parent 9995154832
commit f1ded1168b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 346 additions and 315 deletions
packages/console/src
assets/docs/guides/spa-vanilla
mdx-components
pages
scss

View file

@ -115,7 +115,6 @@ After signing out, it'll be great to redirect user back to your website. Let's a
<UriInputField
appId={props.app.id}
isSingle={!props.isCompact}
name="postLogoutRedirectUris"
title="application_details.post_sign_out_redirect_uri"
/>

View file

@ -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);
}
}

View file

@ -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 (
<div className={classNames(styles.wrapper, isCompact && styles.fullWidth)}>
{!isCompact && (
<div className={styles.navigationAnchor}>
<nav className={styles.navigation}>
{children.map((component, index) => (
<div
key={component.props.title}
role="button"
tabIndex={0}
className={classNames(styles.stepper, index === activeIndex && styles.active)}
onKeyDown={onKeyDownHandler(() => {
navigateToStep(index);
})}
onClick={() => {
navigateToStep(index);
}}
>
{index + 1}. {component.props.title}
</div>
))}
</nav>
</div>
)}
<div ref={contentRef} className={styles.content}>
<div className={styles.wrapper}>
<div ref={contentRef} className={classNames(styles.content, isCompact && styles.compact)}>
{!isCompact && (
<div className={styles.navigationAnchor}>
<nav className={styles.navigation}>
{children.map((component, index) => (
<div
key={component.props.title}
role="button"
tabIndex={0}
className={classNames(styles.stepper, index === activeIndex && styles.active)}
onKeyDown={onKeyDownHandler(() => {
navigateToStep(index);
})}
onClick={() => {
navigateToStep(index);
}}
>
{index + 1}. {component.props.title}
</div>
))}
</nav>
</div>
)}
<Sample />
{children.map((component, index) =>
React.cloneElement(component, {

View file

@ -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<Application, RequestError>(`api/applications/${appId}`);
const ref = useRef<HTMLDivElement>(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 (
<div ref={ref} className={styles.wrapper}>
{isSingle && (
<FormField
isRequired={name === 'redirectUris'}
className={styles.field}
title={title}
>
<TextInput
className={styles.field}
value={value[0]}
error={errorObject?.required ?? errorObject?.inputs?.[0]}
onChange={({ currentTarget: { value } }) => {
onChange([value]);
}}
onKeyPress={(event) => {
onKeyPress(event, value);
}}
/>
</FormField>
)}
{!isSingle && (
<MultiTextInputField
isRequired={name === 'redirectUris'}
formFieldClassName={styles.field}
title={title}
value={value}
error={errorObject}
className={styles.multiTextInput}
onChange={onChange}
onKeyPress={(event) => {
onKeyPress(event, value);
}}
/>
)}
<MultiTextInputField
isRequired={name === 'redirectUris'}
formFieldClassName={styles.field}
title={title}
value={value}
error={errorObject}
className={styles.multiTextInput}
onChange={onChange}
onKeyPress={(event) => {
onKeyPress(event, value);
}}
/>
<Button
className={styles.saveButton}
disabled={!isDirty}

View file

@ -32,6 +32,6 @@
.guide {
flex: 1;
height: unset;
overflow: hidden;
padding: _.unit(6);
}

View file

@ -74,7 +74,6 @@ function GuideDrawer({ app, onClose }: Props) {
</div>
{!selectedGuide && (
<GuideGroup
isCompact
className={styles.cardGroup}
categoryName={t(`categories.${app.type}`)}
guides={structuredMetadata[app.type]}

View file

@ -36,7 +36,7 @@ import useTenantPathname from '@/hooks/use-tenant-pathname';
import { applicationTypeI18nKey } from '@/types/applications';
import { trySubmitSafe } from '@/utils/form';
import GuideModal from '../Applications/components/Guide/GuideModal';
import GuideModal from '../Applications/components/GuideModal';
import AdvancedSettings from './components/AdvancedSettings';
import GuideDrawer from './components/GuideDrawer';

View file

@ -1,60 +1,47 @@
@use '@/scss/underscore' as _;
@use '@/scss/dimensions' as dim;
.container {
display: flex;
flex-direction: column;
background-color: var(--color-base);
height: 100vh;
.content {
flex: 1;
width: 100%;
position: relative;
.content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
overflow-y: auto;
padding: _.unit(6) _.unit(6) max(10vh, 120px);
section p {
font: var(--font-body-2);
margin: _.unit(4) 0;
}
section p {
font: var(--font-body-2);
margin: _.unit(4) 0;
section ul > li,
section ol > li {
font: var(--font-body-2);
margin-block: _.unit(2);
padding-inline-start: _.unit(1);
}
section table {
border-spacing: 0;
border: 1px solid var(--color-border);
font: var(--font-body-2);
tr {
width: 100%;
}
section ul > li,
section ol > li {
font: var(--font-body-2);
margin-block: _.unit(2);
padding-inline-start: _.unit(1);
td,
th {
padding: _.unit(2) _.unit(4);
}
section table {
border-spacing: 0;
border: 1px solid var(--color-border);
font: var(--font-body-2);
thead {
font: var(--font-title-3);
}
tr {
width: 100%;
}
td,
th {
padding: _.unit(2) _.unit(4);
}
thead {
font: var(--font-title-3);
}
tbody td {
border-top: 1px solid var(--color-border);
}
tbody td {
border-top: 1px solid var(--color-border);
}
}
}
.markdownContent {
margin-top: _.unit(6);
}
.notFound {
width: 100%;
@ -64,16 +51,15 @@
}
.actionBar {
position: absolute;
inset: auto 0 0 0;
padding: _.unit(4);
flex-shrink: 0;
padding: _.unit(4) _.unit(6);
background-color: var(--color-layer-1);
box-shadow: var(--shadow-3);
z-index: 1;
.layout {
margin: 0 auto;
max-width: 858px;
max-width: dim.$guide-main-content-max-width;
> button {
margin-right: 0;

View file

@ -9,12 +9,12 @@ import { type GuideMetadata } from '@/assets/docs/guides/types';
import { AppDataContext } from '@/contexts/AppDataProvider';
import Button from '@/ds-components/Button';
import CodeEditor from '@/ds-components/CodeEditor';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import TextLink from '@/ds-components/TextLink';
import useCustomDomain from '@/hooks/use-custom-domain';
import DetailsSummary from '@/mdx-components/DetailsSummary';
import NotFound from '@/pages/NotFound';
import GuideHeader from '../GuideHeader';
import StepsSkeleton from '../StepsSkeleton';
import * as styles from './index.module.scss';
@ -86,9 +86,8 @@ function Guide({ className, guideId, app, isCompact, onClose }: Props) {
);
return (
<div className={classNames(styles.container, className)}>
{!isCompact && <GuideHeader onClose={onClose} />}
<div className={styles.content}>
<>
<OverlayScrollbar className={classNames(styles.content, className)}>
{memorizedContext ? (
<GuideContext.Provider value={memorizedContext}>
<MDXProvider
@ -125,20 +124,20 @@ function Guide({ className, guideId, app, isCompact, onClose }: Props) {
) : (
<NotFound className={styles.notFound} />
)}
{!isCompact && memorizedContext && (
<nav className={styles.actionBar}>
<div className={styles.layout}>
<Button
size="large"
title="applications.guide.finish_and_done"
type="primary"
onClick={onClose}
/>
</div>
</nav>
)}
</div>
</div>
</OverlayScrollbar>
{memorizedContext && (
<nav className={styles.actionBar}>
<div className={styles.layout}>
<Button
size="large"
title="applications.guide.finish_and_done"
type="primary"
onClick={onClose}
/>
</div>
</nav>
)}
</>
);
}

View file

@ -11,9 +11,10 @@
min-width: 220px;
max-width: 460px;
justify-content: space-between;
cursor: pointer;
&.compact {
cursor: pointer;
&.hasButton {
cursor: default;
}
&.hasBorder {

View file

@ -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 (
<div
className={classNames(
styles.card,
hasBorder && styles.hasBorder,
isCompact && styles.compact
hasButton && styles.hasButton
)}
{...(isCompact && {
{...(!hasButton && {
tabIndex: 0,
role: 'button',
onKeyDown: onKeyDownHandler(onClickCard),
onClick: onClickCard,
onKeyDown: onKeyDownHandler(handleClick),
onClick: handleClick,
})}
>
<div className={styles.header}>
@ -77,19 +77,13 @@ function GuideCard({ data, onClick, hasBorder, isCompact }: Props) {
</div>
</div>
</div>
{!isCompact && (
{hasButton && (
<Button
title={
isSubscriptionRequired ? 'upsell.upgrade_plan' : 'applications.guide.start_building'
}
size="small"
onClick={() => {
if (isSubscriptionRequired) {
navigate(subscriptionPage);
} else {
onClick({ id, target, name });
}
}}
onClick={handleClick}
/>
)}
</div>

View file

@ -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<HTMLDivElement>
) {
if (!guides?.length) {
@ -31,8 +31,8 @@ function GuideGroup(
{guides.map((guide) => (
<GuideCard
key={guide.id}
isCompact={isCompact}
hasBorder={hasCardBorder}
hasButton={hasCardButton}
data={guide}
onClick={onClickGuide}
/>

View file

@ -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;
}
}

View file

@ -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 (
<div className={styles.header}>
{isCompact && (
<>
<CardTitle
size="small"
title="applications.guide.modal_header_title"
subtitle="applications.guide.header_subtitle"
/>
<Spacer />
<Tooltip
placement="bottom"
anchorClassName={styles.githubToolTipAnchor}
content={t('applications.guide.get_sample_file')}
/>
<IconButton size="large" onClick={onClose}>
<Close className={styles.closeIcon} />
</IconButton>
</>
)}
{!isCompact && (
<>
<IconButton size="large" onClick={onClose}>
<Close className={styles.closeIcon} />
</IconButton>
<div className={styles.separator} />
<CardTitle
size="small"
title="applications.guide.modal_header_title"
subtitle="applications.guide.header_subtitle"
/>
<Spacer />
<Button
className={styles.requestSdkButton}
type="outline"
icon={<Box />}
title="applications.guide.cannot_find_guide"
onClick={() => {
if (isCloud) {
setIsRequestGuideOpen(true);
} else {
window.open(githubIssuesLink, '_blank');
}
}}
/>
</>
)}
<IconButton size="large" onClick={onClose}>
<Close className={styles.closeIcon} />
</IconButton>
<div className={styles.separator} />
<CardTitle
size="small"
title="applications.guide.modal_header_title"
subtitle="applications.guide.header_subtitle"
/>
<Spacer />
<Button
className={styles.requestSdkButton}
type="outline"
icon={<Box />}
title="applications.guide.cannot_find_guide"
onClick={() => {
if (isCloud) {
setIsRequestGuideOpen(true);
} else {
window.open(githubIssuesLink, '_blank');
}
}}
/>
{isCloud && <RequestGuide isOpen={isRequestGuideOpen} onClose={onRequestGuideClose} />}
</div>
);

View file

@ -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);
}
}

View file

@ -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<string>('');
@ -69,66 +70,71 @@ function GuideLibrary({ className, hasCardBorder, hasFilters }: Props) {
);
return (
<div className={classNames(styles.container, className)}>
{hasFilters && (
<div className={styles.filters}>
<label>{t('filter.title')}</label>
<TextInput
className={styles.searchInput}
icon={<SearchIcon />}
placeholder={t('filter.placeholder')}
value={keyword}
onChange={(event) => {
setKeyword(event.currentTarget.value);
}}
/>
<div className={styles.checkboxGroupContainer}>
<CheckboxGroup
className={styles.checkboxGroup}
options={allAppGuideCategories.map((category) => ({
title: `applications.guide.categories.${category}`,
value: category,
}))}
value={filterCategories}
onChange={(value) => {
const sortedValue = allAppGuideCategories.filter((category) =>
value.includes(category)
);
setFilterCategories(sortedValue);
}}
/>
{isM2mDisabledForCurrentPlan && <ProTag className={styles.proTag} />}
</div>
</div>
)}
{keyword &&
(filteredMetadata?.length ? (
<GuideGroup
className={styles.guideGroup}
hasCardBorder={hasCardBorder}
guides={filteredMetadata}
onClickGuide={onClickGuide}
/>
) : (
<EmptyDataPlaceholder className={styles.emptyPlaceholder} size="large" />
))}
{!keyword && (
<OverlayScrollbar className={styles.groups}>
{(filterCategories.length > 0 ? filterCategories : allAppGuideCategories).map(
(category) =>
structuredMetadata[category].length > 0 && (
<GuideGroup
key={category}
className={styles.guideGroup}
hasCardBorder={hasCardBorder}
categoryName={t(`categories.${category}`)}
guides={structuredMetadata[category]}
onClickGuide={onClickGuide}
<OverlayScrollbar className={classNames(styles.container, className)}>
<div className={classNames(styles.wrapper, hasFilters && styles.hasFilters)}>
<div className={styles.groups}>
{hasFilters && (
<div className={styles.filterAnchor}>
<div className={styles.filters}>
<label>{t('filter.title')}</label>
<TextInput
className={styles.searchInput}
icon={<SearchIcon />}
placeholder={t('filter.placeholder')}
value={keyword}
onChange={(event) => {
setKeyword(event.currentTarget.value);
}}
/>
)
<div className={styles.checkboxGroupContainer}>
<CheckboxGroup
className={styles.checkboxGroup}
options={allAppGuideCategories.map((category) => ({
title: `applications.guide.categories.${category}`,
value: category,
}))}
value={filterCategories}
onChange={(value) => {
const sortedValue = allAppGuideCategories.filter((category) =>
value.includes(category)
);
setFilterCategories(sortedValue);
}}
/>
{isM2mDisabledForCurrentPlan && <ProTag className={styles.proTag} />}
</div>
</div>
</div>
)}
</OverlayScrollbar>
)}
{keyword &&
(filteredMetadata?.length ? (
<GuideGroup
className={styles.guideGroup}
hasCardBorder={hasCardBorder}
hasCardButton={hasCardButton}
guides={filteredMetadata}
onClickGuide={onClickGuide}
/>
) : (
<EmptyDataPlaceholder className={styles.emptyPlaceholder} size="large" />
))}
{!keyword &&
(filterCategories.length > 0 ? filterCategories : allAppGuideCategories).map(
(category) =>
structuredMetadata[category].length > 0 && (
<GuideGroup
key={category}
className={styles.guideGroup}
hasCardBorder={hasCardBorder}
hasCardButton={hasCardButton}
categoryName={t(`categories.${category}`)}
guides={structuredMetadata[category]}
onClickGuide={onClickGuide}
/>
)
)}
</div>
</div>
{selectedGuide?.target !== 'API' && showCreateForm && (
<CreateForm
defaultCreateType={selectedGuide?.target}
@ -136,7 +142,7 @@ function GuideLibrary({ className, hasCardBorder, hasFilters }: Props) {
onClose={onCloseCreateForm}
/>
)}
</div>
</OverlayScrollbar>
);
}

View file

@ -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);
}
}

View file

@ -30,18 +30,19 @@ function GuideLibraryModal({ isOpen, onClose }: Props) {
>
<div className={styles.container}>
<GuideHeader onClose={onClose} />
<GuideLibrary hasFilters className={styles.content} />
<GuideLibrary hasFilters hasCardButton className={styles.content} />
<nav className={styles.actionBar}>
<span className={styles.text}>{t('do_not_need_tutorial')}</span>
<Button
className={styles.button}
size="large"
title="applications.guide.create_without_framework"
type="outline"
onClick={() => {
setShowCreateForm(true);
}}
/>
<div className={styles.wrapper}>
<span className={styles.text}>{t('do_not_need_tutorial')}</span>
<Button
size="large"
title="applications.guide.create_without_framework"
type="outline"
onClick={() => {
setShowCreateForm(true);
}}
/>
</div>
</nav>
</div>
{showCreateForm && (

View file

@ -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;
}

View file

@ -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}
>
<Guide guideId={guideId} app={app} onClose={closeModal} />
<div className={styles.modalContainer}>
<GuideHeader onClose={closeModal} />
<Guide className={styles.guide} guideId={guideId} app={app} onClose={closeModal} />
</div>
</Modal>
);
}

View file

@ -85,7 +85,7 @@ function Applications() {
title="applications.guide.header_title"
subtitle="applications.guide.header_subtitle"
/>
<GuideLibrary hasCardBorder className={styles.library} />
<GuideLibrary hasCardBorder hasCardButton className={styles.library} />
</OverlayScrollbar>
)}
{(isLoading || !!applications?.length) && (

View file

@ -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;