Skip to content

Commit

Permalink
fix(UI): minimize the fixed contents upper the editor (freeCodeCamp#5…
Browse files Browse the repository at this point in the history
  • Loading branch information
mmatsumoto1026 authored Jun 21, 2024
1 parent fd1bf0d commit 2847c7c
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 54 deletions.
1 change: 1 addition & 0 deletions client/config/misc.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const MAX_MOBILE_WIDTH = 767;
export const EX_SMALL_VIEWPORT_HEIGHT = 300;
export const TOOL_PANEL_HEIGHT = 37;
export const SEARCH_EXPOSED_WIDTH = 980;
23 changes: 21 additions & 2 deletions client/src/components/layouts/default.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { ReactNode, useEffect } from 'react';
import Helmet from 'react-helmet';
import { useTranslation, withTranslation } from 'react-i18next';
import { useMediaQuery } from 'react-responsive';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { createSelector } from 'reselect';
Expand Down Expand Up @@ -43,7 +44,11 @@ import StagingWarningModal from '../staging-warning-modal';
import Footer from '../Footer';
import Header from '../Header';
import OfflineWarning from '../OfflineWarning';
import { Loader } from '../helpers';
import { Loader, Spacer } from '../helpers';
import {
MAX_MOBILE_WIDTH,
EX_SMALL_VIEWPORT_HEIGHT
} from '../../../config/misc';
import envData from '../../../config/env.json';

import '@freecodecamp/ui/dist/base.css';
Expand Down Expand Up @@ -103,6 +108,7 @@ interface DefaultLayoutProps extends StateProps, DispatchProps {
pathname: string;
showFooter?: boolean;
isChallenge?: boolean;
usesMultifileEditor?: boolean;
block?: string;
examInProgress: boolean;
superBlock?: string;
Expand All @@ -127,6 +133,7 @@ function DefaultLayout({
removeFlashMessage,
showFooter = true,
isChallenge = false,
usesMultifileEditor,
block,
superBlock,
theme,
Expand All @@ -135,6 +142,14 @@ function DefaultLayout({
updateAllChallengesInfo
}: DefaultLayoutProps): JSX.Element {
const { t } = useTranslation();
const isMobileLayout = useMediaQuery({ maxWidth: MAX_MOBILE_WIDTH });
const isProject = /project$/.test(block as string);
const isRenderBreadcrumbOnMobile =
isMobileLayout && (isProject || !usesMultifileEditor);
const isRenderBreadcrumb = !isMobileLayout || isRenderBreadcrumbOnMobile;
const isExSmallViewportHeight = useMediaQuery({
maxHeight: EX_SMALL_VIEWPORT_HEIGHT
});
const { challengeEdges, certificateNodes } = useGetAllBlockIds();
useEffect(() => {
// componentDidMount
Expand Down Expand Up @@ -243,13 +258,17 @@ function DefaultLayout({
/>
) : null}
<SignoutModal />
{isChallenge && !examInProgress && (
{isChallenge && !examInProgress && isRenderBreadcrumb ? (
<div className='breadcrumbs-demo'>
<BreadCrumb
block={block as string}
superBlock={superBlock as string}
/>
</div>
) : isExSmallViewportHeight ? (
<Spacer size='xxSmall' />
) : (
<Spacer size='small' />
)}
{fetchState.complete && children}
</div>
Expand Down
53 changes: 52 additions & 1 deletion client/src/templates/Challenges/classic/classic.css
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,16 @@

#mobile-layout .portal-button-wrap {
position: absolute;
top: calc(var(--header-height) + var(--breadcrumbs-height));
top: auto;
right: 0;
height: 2rem;
border-bottom: 1px solid var(--quaternary-color);
}

#mobile-layout .tab-content[data-state='active'] .portal-button-wrap {
transform: translateY(-2rem);
}

#mobile-layout #mobile-layout-pane-instructions {
overflow-y: auto;
}
Expand Down Expand Up @@ -224,3 +228,50 @@
.nav-lists [role='tab']:focus {
outline-offset: -3px;
}

@media screen and (max-width: 480px) {
#mobile-layout .monaco-editor-tabs {
padding: 10px 5px;
}
}

@media screen and (max-height: 300px) {
#mobile-layout .nav-lists {
height: 1.5rem;
}

#mobile-layout .nav-lists > button {
display: flex;
justify-content: center;
align-items: center;
}

#mobile-layout .portal-button-wrap {
height: 1.5rem;
}

#mobile-layout .tab-content[data-state='active'] .portal-button-wrap {
height: 1.52rem;
transform: translateY(-1.5rem);
}

#mobile-layout .portal-button-wrap button {
display: flex;
justify-content: center;
align-items: center;
height: 1.43rem;
font-size: initial;
}

#mobile-layout .portal-button-wrap button svg {
scale: 0.8;
}

#mobile-layout .monaco-editor-tabs {
padding: 5px;
}

.monaco-editor-tabs button {
padding: 2px 12px;
}
}
56 changes: 33 additions & 23 deletions client/src/templates/Challenges/classic/editor-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
challengeFilesSelector
} from '../redux/selectors';

import { MAX_MOBILE_WIDTH } from '../../../../config/misc';
import type { VisibleEditors } from './multifile-editor';

interface EditorTabsProps {
Expand All @@ -34,32 +35,41 @@ const mapDispatchToProps = {

class EditorTabs extends Component<EditorTabsProps> {
static displayName: string;
isMobile = window.innerWidth < MAX_MOBILE_WIDTH;
render() {
const { challengeFiles, toggleVisibleEditor, visibleEditors } = this.props;
const isMobile = window.innerWidth < MAX_MOBILE_WIDTH;
const isRenderChallengeFiles =
/* eslint-disable-next-line @typescript-eslint/no-unsafe-member-access */
!isMobile || sortChallengeFiles(challengeFiles).length > 1;
return (
<div className='monaco-editor-tabs'>
{/* eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */}
{sortChallengeFiles(challengeFiles).map(
(challengeFile: ChallengeFile) => (
<button
aria-expanded={
// @ts-expect-error TODO: validate challengeFile on io-boundary,
// then we won't need to ignore this error and we can drop the
// nullish handling.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
visibleEditors[challengeFile.fileKey] ?? 'false'
}
key={challengeFile.fileKey}
onClick={() => toggleVisibleEditor(challengeFile.fileKey)}
>
{`${challengeFile.name}.${challengeFile.ext}`}{' '}
<span className='sr-only'>
{i18next.t('learn.editor-tabs.editor')}
</span>
</button>
)
)}
</div>
isRenderChallengeFiles && (
<div className='monaco-editor-tabs'>
{
/* eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
sortChallengeFiles(challengeFiles).map(
(challengeFile: ChallengeFile) => (
<button
aria-expanded={
// @ts-expect-error TODO: validate challengeFile on io-boundary,
// then we won't need to ignore this error and we can drop the
// nullish handling.
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
visibleEditors[challengeFile.fileKey] ?? 'false'
}
key={challengeFile.fileKey}
onClick={() => toggleVisibleEditor(challengeFile.fileKey)}
>
{`${challengeFile.name}.${challengeFile.ext}`}{' '}
<span className='sr-only'>
{i18next.t('learn.editor-tabs.editor')}
</span>
</button>
)
)
}
</div>
)
);
}
}
Expand Down
52 changes: 52 additions & 0 deletions client/src/templates/Challenges/classic/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ textarea.inputarea {
height: var(--breadcrumbs-height);
}

@media screen and (max-height: 300px) {
.default-layout:has(#mobile-layout) .breadcrumbs-demo {
height: auto;
padding-block: 2px;
}
}

.editor-upper-jaw,
.editor-lower-jaw {
padding-inline: 0 15px;
Expand Down Expand Up @@ -65,6 +72,51 @@ textarea.inputarea {
margin: 0.1em 0 0.6rem;
}

.description-container .breadcrumbs {
display: flex;
justify-content: start;
gap: 1rem;
list-style-type: none;
font-size: 16px;
text-align: center;
margin-bottom: 0.8rem;
padding-inline-start: 0;
width: 100%;
min-width: 0;
color: var(--foreground-quaternary);
}

.description-container .breadcrumbs li:first-child {
position: relative;
max-width: 35%;
}

.description-container .breadcrumbs li:first-child:after {
content: ' >';
font-size: 17px;
position: absolute;
top: 0;
right: -0.7rem;
}

.description-container .breadcrumbs li:nth-child(2) {
min-width: 0;
}

.description-container .breadcrumbs a {
width: 100%;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
border-bottom: 1px solid var(--foreground-quaternary);
}

.description-container .breadcrumbs a:focus {
background-color: inherit;
}

.description-container h1 {
color: var(--secondary-color);
font-size: 1.1rem;
Expand Down
34 changes: 33 additions & 1 deletion client/src/templates/Challenges/classic/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export interface EditorProps {
challengeFiles: ChallengeFiles;
challengeType: number;
containerRef?: React.RefObject<HTMLElement>;
block: string;
superBlock: string;
description: string;
dimensions?: Dimensions;
editorRef: MutableRefObject<editor.IStandaloneCodeEditor | undefined>;
Expand Down Expand Up @@ -243,7 +245,7 @@ const initialData: EditorProperties = {
const Editor = (props: EditorProps): JSX.Element => {
const reduxStore = useStore();
const { t } = useTranslation();
const { editorRef, initTests, resetAttempts } = props;
const { editorRef, initTests, resetAttempts, isMobileLayout } = props;
// These refs are used during initialisation of the editor as well as by
// callbacks. Since they have to be initialised before editorWillMount and
// editorDidMount are called, we cannot use useState. Reason being that will
Expand Down Expand Up @@ -804,6 +806,7 @@ const Editor = (props: EditorProps): JSX.Element => {
descContainer.classList.add('description-container');
domNode.classList.add('editor-upper-jaw');
domNode.appendChild(descContainer);
if (isMobileLayout) descContainer.appendChild(createBreadcrumb());
descContainer.appendChild(jawHeading);
descContainer.appendChild(desc);
desc.innerHTML = description;
Expand Down Expand Up @@ -896,6 +899,35 @@ const Editor = (props: EditorProps): JSX.Element => {
updateFile({ fileKey, editorValue, editableRegionBoundaries });
};

function createBreadcrumb(): HTMLElement {
const { block, superBlock } = props;
const breadcrumb = document.createElement('nav');
breadcrumb.setAttribute('aria-label', `${t('aria.breadcrumb-nav')}`);
const breadcrumbList = document.createElement('ol'),
breadcrumbLeft = document.createElement('li'),
breadcrumbLeftLink = document.createElement('a'),
breadcrumbRight = document.createElement('li'),
breadcrumbRightLink = document.createElement('a');
breadcrumbLeftLink.innerHTML = t(`intro:${superBlock}.title`);
breadcrumbRightLink.innerHTML = t(
`intro:${superBlock}.blocks.${block}.title`
);
breadcrumbLeftLink.setAttribute('href', `/learn/${superBlock}`);
breadcrumbRightLink.setAttribute('href', `/learn/${superBlock}/#${block}`);
breadcrumbLeft.appendChild(breadcrumbLeftLink);
breadcrumbRight.appendChild(breadcrumbRightLink);
breadcrumbList.setAttribute(
'data-playwright-test-label',
'breadcrumb-mobile'
);
breadcrumbList.className = 'breadcrumbs';
breadcrumbList.appendChild(breadcrumbLeft);
breadcrumbList.appendChild(breadcrumbRight);
breadcrumb.appendChild(breadcrumbList);

return breadcrumb;
}

// TODO: DRY this and the update function
function initializeEditableRegion(
range: IRange,
Expand Down
6 changes: 6 additions & 0 deletions client/src/templates/Challenges/classic/multifile-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type MultifileEditorProps = Pick<
| 'initialTests'
| 'editorRef'
| 'containerRef'
| 'block'
| 'superBlock'
| 'challengeFiles'
| 'description'
// We use dimensions to trigger a re-render of the editor
Expand Down Expand Up @@ -65,6 +67,8 @@ const mapStateToProps = createSelector(

const MultifileEditor = (props: MultifileEditorProps) => {
const {
block,
superBlock,
challengeFiles,
containerRef,
description,
Expand Down Expand Up @@ -133,6 +137,8 @@ const MultifileEditor = (props: MultifileEditorProps) => {
>
<Editor
canFocusOnMountRef={canFocusOnMountRef}
block={block}
superBlock={superBlock}
challengeFiles={challengeFiles}
containerRef={containerRef}
description={targetEditor === key ? description : ''}
Expand Down
2 changes: 2 additions & 0 deletions client/src/templates/Challenges/classic/show.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ function ShowClassic({
reduxChallengeFiles && (
<MultifileEditor
challengeFiles={reduxChallengeFiles}
block={block}
superBlock={superBlock}
containerRef={containerRef}
description={description}
editorRef={editorRef}
Expand Down
2 changes: 1 addition & 1 deletion client/src/templates/Challenges/components/bread-crumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function BreadCrumb({ block, superBlock }: BreadCrumbProps): JSX.Element {
className='challenge-title-breadcrumbs'
aria-label={t('aria.breadcrumb-nav')}
>
<ol>
<ol data-playwright-test-label='breadcrumb-desktop'>
<li className='breadcrumb-left'>
<Link
state={{ breadcrumbBlockClick: block }}
Expand Down
Loading

0 comments on commit 2847c7c

Please sign in to comment.