fix(learn): implemented curriculum layout for mobile devices (#17467)

remove useless changes
This commit is contained in:
Dmytro Yarmak
2018-12-07 14:08:32 +02:00
committed by Stuart Taylor
parent 37d98f1123
commit d4b07f47ab
18 changed files with 382 additions and 108 deletions

View File

@ -624,4 +624,4 @@ pre tt:after {
} }
.default-layout { .default-layout {
margin-top: 38px; margin-top: 38px;
} }

View File

@ -25,6 +25,13 @@
padding: 5px 10px 5px 10px; padding: 5px 10px 5px 10px;
} }
@media screen, (max-width: 767px) {
#learn-app-wrapper {
right: -2em;
padding: 2px 0 0 0;
}
}
.reflex-layout.reflex-container.vertical { .reflex-layout.reflex-container.vertical {
width: 100%; width: 100%;
margin: 0 18px; margin: 0 18px;

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
@ -7,6 +7,8 @@ import Helmet from 'react-helmet';
import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex'; import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex';
import { graphql } from 'gatsby'; import { graphql } from 'gatsby';
import { first } from 'lodash'; import { first } from 'lodash';
import { Tabs, TabPane } from '@freecodecamp/react-bootstrap';
import Media from 'react-media';
import LearnLayout from '../../../components/layouts/Learn'; import LearnLayout from '../../../components/layouts/Learn';
import Editor from './Editor'; import Editor from './Editor';
@ -17,6 +19,7 @@ import CompletionModal from '../components/CompletionModal';
import HelpModal from '../components/HelpModal'; import HelpModal from '../components/HelpModal';
import VideoModal from '../components/VideoModal'; import VideoModal from '../components/VideoModal';
import ResetModal from '../components/ResetModal'; import ResetModal from '../components/ResetModal';
import ToolPanel from '../components/Tool-Panel';
import { randomCompliment } from '../utils/get-words'; import { randomCompliment } from '../utils/get-words';
import { createGuideUrl } from '../utils'; import { createGuideUrl } from '../utils';
@ -27,11 +30,13 @@ import {
createFiles, createFiles,
challengeFilesSelector, challengeFilesSelector,
challengeTestsSelector, challengeTestsSelector,
currentTabSelector,
initTests, initTests,
updateChallengeMeta, updateChallengeMeta,
challengeMounted, challengeMounted,
updateSuccessMessage, updateSuccessMessage,
consoleOutputSelector consoleOutputSelector,
moveToTab
} from '../redux'; } from '../redux';
import './classic.css'; import './classic.css';
@ -43,16 +48,19 @@ const mapStateToProps = createSelector(
challengeFilesSelector, challengeFilesSelector,
challengeTestsSelector, challengeTestsSelector,
consoleOutputSelector, consoleOutputSelector,
(files, tests, output) => ({ currentTabSelector,
(files, tests, output, currentTab) => ({
files, files,
tests, tests,
output output,
currentTab
}) })
); );
const mapDispatchToProps = dispatch => const mapDispatchToProps = dispatch =>
bindActionCreators( bindActionCreators(
{ {
moveToTab,
createFiles, createFiles,
initTests, initTests,
updateChallengeMeta, updateChallengeMeta,
@ -65,6 +73,7 @@ const mapDispatchToProps = dispatch =>
const propTypes = { const propTypes = {
challengeMounted: PropTypes.func.isRequired, challengeMounted: PropTypes.func.isRequired,
createFiles: PropTypes.func.isRequired, createFiles: PropTypes.func.isRequired,
currentTab: PropTypes.number,
data: PropTypes.shape({ data: PropTypes.shape({
challengeNode: ChallengeNode challengeNode: ChallengeNode
}), }),
@ -72,6 +81,7 @@ const propTypes = {
key: PropTypes.string key: PropTypes.string
}), }),
initTests: PropTypes.func.isRequired, initTests: PropTypes.func.isRequired,
moveToTab: PropTypes.func.isRequired,
output: PropTypes.string, output: PropTypes.string,
pageContext: PropTypes.shape({ pageContext: PropTypes.shape({
challengeMeta: PropTypes.shape({ challengeMeta: PropTypes.shape({
@ -88,6 +98,8 @@ const propTypes = {
updateSuccessMessage: PropTypes.func.isRequired updateSuccessMessage: PropTypes.func.isRequired
}; };
const MAX_MOBILE_WIDTH = 767;
class ShowClassic extends Component { class ShowClassic extends Component {
constructor() { constructor() {
super(); super();
@ -101,7 +113,6 @@ class ShowClassic extends Component {
resizing: false resizing: false
}; };
} }
onResize() { onResize() {
this.setState({ resizing: true }); this.setState({ resizing: true });
} }
@ -169,88 +180,222 @@ class ShowClassic extends Component {
} }
} }
render() { getBlockNameTitle() {
const { const {
data: { data: {
challengeNode: { challengeNode: {
challengeType, fields: { blockName },
fields: { blockName, slug }, title
title, }
}
} = this.props;
return `${blockName}: ${title}`;
}
getGuideUrl() {
const {
data: {
challengeNode: {
fields: { slug }
}
}
} = this.props;
return createGuideUrl(slug);
}
getChallengeFile() {
const { files } = this.props;
return first(Object.keys(files).map(key => files[key]));
}
hasPreview() {
const {
data: {
challengeNode: {
challengeType
}
}
} = this.props;
return (
challengeType === challengeTypes.html ||
challengeType === challengeTypes.modern
);
}
renderInstructionsPanel({ showToolPanel }) {
const {
data: {
challengeNode: {
fields: { blockName },
description, description,
instructions, instructions,
videoUrl videoUrl
} }
}, }
files,
output
} = this.props; } = this.props;
return (
<SidePanel
className='full-height'
description={description}
guideUrl={this.getGuideUrl()}
instructions={instructions}
section={dasherize(blockName)}
showToolPanel={showToolPanel}
title={this.getBlockNameTitle()}
videoUrl={videoUrl}
/>
);
}
renderEditor() {
const { files } = this.props;
const challengeFile = first(Object.keys(files).map(key => files[key])); const challengeFile = first(Object.keys(files).map(key => files[key]));
const editors = challengeFile && ( return challengeFile && (
<ReflexContainer key={challengeFile.key} orientation='horizontal'> <Editor {...challengeFile} fileKey={challengeFile.key} />
<ReflexElement );
flex={1} }
propagateDimensions={true}
renderOnResize={true} renderTestOutput() {
renderOnResizeRate={20} const { output } = this.props;
{...this.resizeProps} return (
> <Output
<Editor {...challengeFile} fileKey={challengeFile.key} />
</ReflexElement>
<ReflexSplitter propagate={true} {...this.resizeProps} />
<ReflexElement
flex={0.25}
propagateDimensions={true}
renderOnResize={true}
renderOnResizeRate={20}
{...this.resizeProps}
>
<Output
defaultOutput={` defaultOutput={`
/** /**
* Your test output will go here. * Your test output will go here.
*/ */
`} `}
output={decodeHTMLEntities(output)} output={decodeHTMLEntities(output)}
/> />
);
}
renderPreview() {
return (
<Preview
className='full-height'
disableIframe={this.state.resizing}
/>
);
}
renderDesktopLayout() {
const challengeFile = this.getChallengeFile();
return (
<ReflexContainer orientation='vertical'>
<ReflexElement flex={1} {...this.resizeProps}>
{this.renderInstructionsPanel({ showToolPanel: true })}
</ReflexElement> </ReflexElement>
<ReflexSplitter propagate={true} {...this.resizeProps} />
<ReflexElement flex={1} {...this.resizeProps}>
{challengeFile && (
<ReflexContainer key={challengeFile.key} orientation='horizontal'>
<ReflexElement
flex={1}
propagateDimensions={true}
renderOnResize={true}
renderOnResizeRate={20}
{...this.resizeProps}
>
{this.renderEditor()}
</ReflexElement>
<ReflexSplitter propagate={true} {...this.resizeProps} />
<ReflexElement
flex={0.25}
propagateDimensions={true}
renderOnResize={true}
renderOnResizeRate={20}
{...this.resizeProps}
>
{this.renderTestOutput()}
</ReflexElement>
</ReflexContainer>
)}
</ReflexElement>
{this.hasPreview() && (
<Fragment>
<ReflexSplitter propagate={true} {...this.resizeProps} />
<ReflexElement flex={0.7} {...this.resizeProps}>
{this.renderPreview()}
</ReflexElement>
</Fragment>
)}
</ReflexContainer> </ReflexContainer>
); );
const showPreview = }
challengeType === challengeTypes.html ||
challengeType === challengeTypes.modern; renderMobileLayout() {
const blockNameTitle = `${blockName}: ${title}`; const {
data: {
challengeNode: {
videoUrl
}
},
currentTab,
moveToTab
} = this.props;
const editorTabPaneProps = {
mountOnEnter: true,
unmountOnExit: true
};
return (
<Fragment>
<Tabs
activeKey={currentTab}
defaultActiveKey={1}
id='challege-page-tabs'
onSelect={(key) => moveToTab(key)}
>
<TabPane eventKey={1} title='Instructions'>
{ this.renderInstructionsPanel({ showToolPanel: false }) }
</TabPane>
<TabPane eventKey={2} title='Code' {...editorTabPaneProps}>
<div className='challege-edittor-wrapper'>
{this.renderEditor()}
</div>
</TabPane>
<TabPane eventKey={3} title='Tests' {...editorTabPaneProps}>
<div className='challege-edittor-wrapper'>
{this.renderTestOutput()}
</div>
</TabPane>
{this.hasPreview() && (
<TabPane eventKey={4} title='Preview'>
{this.renderPreview()}
</TabPane>
)}
</Tabs>
<ToolPanel
guideUrl={this.getGuideUrl()}
isMobile={true}
videoUrl={videoUrl}
/>
</Fragment>
);
}
render() {
const {
data: {
challengeNode: {
videoUrl
}
}
} = this.props;
return ( return (
<LearnLayout> <LearnLayout>
<Helmet title={`Learn ${blockNameTitle} | freeCodeCamp.org`} /> <Helmet
<ReflexContainer orientation='vertical'> title={`Learn ${this.getBlockNameTitle()} | freeCodeCamp.org`}
<ReflexElement flex={1} {...this.resizeProps}> />
<SidePanel <Media query={{ maxWidth: MAX_MOBILE_WIDTH }}>
className='full-height' {matches =>
description={description} matches
guideUrl={createGuideUrl(slug)} ? this.renderMobileLayout()
instructions={instructions} : this.renderDesktopLayout()
section={dasherize(blockName)} }
title={blockNameTitle} </Media>
videoUrl={videoUrl}
/>
</ReflexElement>
<ReflexSplitter propagate={true} {...this.resizeProps} />
<ReflexElement flex={1} {...this.resizeProps}>
{editors}
</ReflexElement>
{showPreview && (
<ReflexSplitter propagate={true} {...this.resizeProps} />
)}
{showPreview ? (
<ReflexElement flex={0.7} {...this.resizeProps}>
<Preview
className='full-height'
disableIframe={this.state.resizing}
/>
</ReflexElement>
) : null}
</ReflexContainer>
<CompletionModal /> <CompletionModal />
<HelpModal /> <HelpModal />
<VideoModal videoUrl={videoUrl} /> <VideoModal videoUrl={videoUrl} />

View File

@ -13,6 +13,12 @@
overflow-y: auto; overflow-y: auto;
} }
@media screen, (max-width: 767px) {
.instructions-panel {
height: calc(100vh - 112px);
}
}
.react-monaco-editor-container { .react-monaco-editor-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -23,4 +29,23 @@
.monaco-menu .action-label { .monaco-menu .action-label {
color: #a2bd9b; color: #a2bd9b;
letter-spacing: 0.02em; letter-spacing: 0.02em;
} }
#challege-page-tabs .nav-tabs {
margin-left: 2px;
display: flex;
}
#challege-page-tabs .nav-tabs > li {
flex: 1;
text-align: center;
}
#challege-page-tabs .nav-tabs > li > a {
padding: 5px 10px;
}
.challege-edittor-wrapper {
height: calc(100vh - 112px);
overflow: hidden;
}

View File

@ -112,7 +112,7 @@ export class CompletionModal extends Component {
bsStyle='primary' bsStyle='primary'
onClick={submitChallenge} onClick={submitChallenge}
> >
Submit and go to next challenge (Ctrl + Enter) Submit and go to next challenge <span className='hidden-xs'>(Ctrl + Enter)</span>
</Button> </Button>
{showDownloadButton ? ( {showDownloadButton ? (
<Button <Button

View File

@ -7,6 +7,8 @@ import { Button, Modal } from '@freecodecamp/react-bootstrap';
import ga from '../../../analytics'; import ga from '../../../analytics';
import { createQuestion, closeModal, isHelpModalOpenSelector } from '../redux'; import { createQuestion, closeModal, isHelpModalOpenSelector } from '../redux';
import './help-modal.css';
const mapStateToProps = state => ({ isOpen: isHelpModalOpenSelector(state) }); const mapStateToProps = state => ({ isOpen: isHelpModalOpenSelector(state) });
const mapDispatchToProps = dispatch => const mapDispatchToProps = dispatch =>
bindActionCreators( bindActionCreators(
@ -31,7 +33,7 @@ export class HelpModal extends Component {
ga.modalview('/help-modal'); ga.modalview('/help-modal');
} }
return ( return (
<Modal onHide={closeHelpModal} show={isOpen}> <Modal dialogClassName='help-modal' onHide={closeHelpModal} show={isOpen}>
<Modal.Header <Modal.Header
className='help-modal-header fcc-modal' className='help-modal-header fcc-modal'
closeButton={true} closeButton={true}

View File

@ -1,13 +1,26 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { previewMounted } from '../redux';
import './preview.css'; import './preview.css';
const mainId = 'fcc-main-frame'; const mainId = 'fcc-main-frame';
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
previewMounted
},
dispatch
);
const propTypes = { const propTypes = {
className: PropTypes.string, className: PropTypes.string,
disableIframe: PropTypes.bool disableIframe: PropTypes.bool,
previewMounted: PropTypes.func.isRequired
}; };
class Preview extends Component { class Preview extends Component {
@ -19,6 +32,10 @@ class Preview extends Component {
}; };
} }
componentDidMount() {
this.props.previewMounted();
}
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.props.disableIframe !== prevProps.disableIframe) { if (this.props.disableIframe !== prevProps.disableIframe) {
// eslint-disable-next-line react/no-did-update-set-state // eslint-disable-next-line react/no-did-update-set-state
@ -39,4 +56,7 @@ class Preview extends Component {
Preview.displayName = 'Preview'; Preview.displayName = 'Preview';
Preview.propTypes = propTypes; Preview.propTypes = propTypes;
export default Preview; export default connect(
null,
mapDispatchToProps
)(Preview);

View File

@ -34,6 +34,7 @@ const propTypes = {
initConsole: PropTypes.func.isRequired, initConsole: PropTypes.func.isRequired,
instructions: PropTypes.string, instructions: PropTypes.string,
section: PropTypes.string, section: PropTypes.string,
showToolPanel: PropTypes.bool,
tests: PropTypes.arrayOf(PropTypes.object), tests: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string, title: PropTypes.string,
videoUrl: PropTypes.string videoUrl: PropTypes.string
@ -89,6 +90,7 @@ export class SidePanel extends Component {
guideUrl, guideUrl,
tests, tests,
section, section,
showToolPanel,
videoUrl videoUrl
} = this.props; } = this.props;
return ( return (
@ -99,7 +101,9 @@ export class SidePanel extends Component {
<ChallengeTitle>{title}</ChallengeTitle> <ChallengeTitle>{title}</ChallengeTitle>
<ChallengeDescription description={description} instructions={instructions} section={section} /> <ChallengeDescription description={description} instructions={instructions} section={section} />
</div> </div>
<ToolPanel guideUrl={guideUrl} videoUrl={videoUrl} /> { showToolPanel && (
<ToolPanel guideUrl={guideUrl} videoUrl={videoUrl} />
)}
<TestSuite tests={tests} /> <TestSuite tests={tests} />
</div> </div>
); );

View File

@ -23,6 +23,7 @@ const mapDispatchToProps = dispatch =>
const propTypes = { const propTypes = {
executeChallenge: PropTypes.func.isRequired, executeChallenge: PropTypes.func.isRequired,
guideUrl: PropTypes.string, guideUrl: PropTypes.string,
isMobile: PropTypes.bool,
openHelpModal: PropTypes.func.isRequired, openHelpModal: PropTypes.func.isRequired,
openResetModal: PropTypes.func.isRequired, openResetModal: PropTypes.func.isRequired,
openVideoModal: PropTypes.func.isRequired, openVideoModal: PropTypes.func.isRequired,
@ -31,6 +32,7 @@ const propTypes = {
function ToolPanel({ function ToolPanel({
executeChallenge, executeChallenge,
isMobile,
openHelpModal, openHelpModal,
openVideoModal, openVideoModal,
openResetModal, openResetModal,
@ -39,9 +41,11 @@ function ToolPanel({
}) { }) {
return ( return (
<Fragment> <Fragment>
<div className='tool-panel-group'> <div className={`tool-panel-group ${
isMobile ? 'tool-panel-group-mobile' : ''
}`}>
<Button block={true} bsStyle='primary' onClick={executeChallenge}> <Button block={true} bsStyle='primary' onClick={executeChallenge}>
Run the Tests {isMobile ? 'Run' : 'Run the Tests'}
</Button> </Button>
<Button <Button
block={true} block={true}
@ -49,7 +53,7 @@ function ToolPanel({
className='btn-primary-invert' className='btn-primary-invert'
onClick={openResetModal} onClick={openResetModal}
> >
Reset All Code {isMobile ? 'Reset' : 'Reset All Code'}
</Button> </Button>
{guideUrl ? ( {guideUrl ? (
<Button <Button
@ -59,7 +63,7 @@ function ToolPanel({
href={guideUrl} href={guideUrl}
target='_blank' target='_blank'
> >
Get a hint {isMobile ? 'Hint' : 'Get a hint'}
</Button> </Button>
) : null} ) : null}
{videoUrl ? ( {videoUrl ? (
@ -69,7 +73,7 @@ function ToolPanel({
className='btn-primary-invert' className='btn-primary-invert'
onClick={openVideoModal} onClick={openVideoModal}
> >
Watch a video {isMobile ? 'Video' : 'Watch a video'}
</Button> </Button>
) : null} ) : null}
<Button <Button
@ -78,7 +82,7 @@ function ToolPanel({
className='btn-primary-invert' className='btn-primary-invert'
onClick={openHelpModal} onClick={openHelpModal}
> >
Ask for help {isMobile ? 'Help' : 'Ask for help'}
</Button> </Button>
</div> </div>
</Fragment> </Fragment>

View File

@ -9,3 +9,9 @@
height: 30vh; height: 30vh;
width: 30vh; width: 30vh;
} }
@media screen, (max-width: 767px) {
.challenge-success-modal .btn-lg {
font-size: 16px;
}
}

View File

@ -0,0 +1,5 @@
@media screen, (max-width: 767px) {
.help-modal .btn-lg {
font-size: 16px;
}
}

View File

@ -3,6 +3,13 @@
width: 100%; width: 100%;
padding: 0; padding: 0;
margin: 0; margin: 0;
border: none;
}
@media screen, (max-width: 767px) {
.challenge-preview, .challenge-preview-frame {
height: calc(100vh - 112px);
}
} }
.enable-iframe { .enable-iframe {

View File

@ -8,4 +8,10 @@
color: #fff; color: #fff;
font-size: 28px; font-size: 28px;
text-shadow: none; text-shadow: none;
} }
@media screen, (max-width: 767px) {
.reset-modal .btn-lg {
font-size: 16px;
}
}

View File

@ -1,3 +1,17 @@
.tool-panel-group button, .tool-panel-group a { .tool-panel-group button, .tool-panel-group a {
font-size: 1.1rem; font-size: 1.1rem;
} }
.tool-panel-group-mobile button, .tool-panel-group-mobile a {
font-size: 16px;
}
.tool-panel-group-mobile {
display: flex;
flex-direction: row-reverse;
padding: 0 2px;
background: #fff;
}
.tool-panel-group-mobile .btn-block + .btn-block {
margin: 0 2px 0 0;
}

View File

@ -43,7 +43,12 @@ const executeDebounceTimeout = 750;
function updateMainEpic(action$, state$, { document }) { function updateMainEpic(action$, state$, { document }) {
return action$.pipe( return action$.pipe(
ofType(types.updateFile, types.challengeMounted), ofType(
types.updateFile,
types.previewMounted,
types.challengeMounted,
types.resetChallenge
),
filter(() => { filter(() => {
const { challengeType } = challengeMetaSelector(state$.value); const { challengeType } = challengeMetaSelector(state$.value);
return ( return (

View File

@ -67,6 +67,7 @@ export const types = createTypes(
'closeModal', 'closeModal',
'openModal', 'openModal',
'previewMounted',
'challengeMounted', 'challengeMounted',
'checkChallenge', 'checkChallenge',
'executeChallenge', 'executeChallenge',
@ -74,6 +75,8 @@ export const types = createTypes(
'submitChallenge', 'submitChallenge',
'submitComplete', 'submitComplete',
'moveToTab',
...createAsyncTypes('fetchIdToNameMap') ...createAsyncTypes('fetchIdToNameMap')
], ],
ns ns
@ -143,6 +146,7 @@ export const noStoredCodeFound = createAction(types.noStoredCodeFound);
export const closeModal = createAction(types.closeModal); export const closeModal = createAction(types.closeModal);
export const openModal = createAction(types.openModal); export const openModal = createAction(types.openModal);
export const previewMounted = createAction(types.challengeMounted);
export const challengeMounted = createAction(types.challengeMounted); export const challengeMounted = createAction(types.challengeMounted);
export const checkChallenge = createAction(types.checkChallenge); export const checkChallenge = createAction(types.checkChallenge);
export const executeChallenge = createAction(types.executeChallenge); export const executeChallenge = createAction(types.executeChallenge);
@ -150,6 +154,9 @@ export const resetChallenge = createAction(types.resetChallenge);
export const submitChallenge = createAction(types.submitChallenge); export const submitChallenge = createAction(types.submitChallenge);
export const submitComplete = createAction(types.submitComplete); export const submitComplete = createAction(types.submitComplete);
export const moveToTab = createAction(types.moveToTab);
export const currentTabSelector = state => state[ns].currentTab;
export const challengeFilesSelector = state => state[ns].challengeFiles; export const challengeFilesSelector = state => state[ns].challengeFiles;
export const challengeIdToNameMapSelector = state => export const challengeIdToNameMapSelector = state =>
state[ns].challengeIdToNameMap; state[ns].challengeIdToNameMap;
@ -234,6 +241,7 @@ export const reducer = handleActions(
[types.resetChallenge]: state => ({ [types.resetChallenge]: state => ({
...state, ...state,
currentTab: 2,
challengeFiles: { challengeFiles: {
...Object.keys(state.challengeFiles) ...Object.keys(state.challengeFiles)
.map(key => state.challengeFiles[key]) .map(key => state.challengeFiles[key])
@ -291,6 +299,14 @@ export const reducer = handleActions(
...state.modal, ...state.modal,
[payload]: true [payload]: true
} }
}),
[types.moveToTab]: (state, { payload }) => ({
...state,
currentTab: payload
}),
[types.executeChallenge]: (state, { payload }) => ({
...state,
currentTab: 3
}) })
}, },
initialState initialState

View File

@ -44,31 +44,33 @@ function IntroductionPage({ data: { markdownRemark, allChallengeNode } }) {
<Helmet> <Helmet>
<title>{block} | freeCodeCamp</title> <title>{block} | freeCodeCamp</title>
</Helmet> </Helmet>
<FullWidthRow> <div className='intro-layout-container'>
<div <FullWidthRow>
className='intro-layout' <div
dangerouslySetInnerHTML={{ __html: html }} className='intro-layout'
/> dangerouslySetInnerHTML={{ __html: html }}
</FullWidthRow> />
<FullWidthRow> </FullWidthRow>
<Link className='btn btn-lg btn-primary btn-block' to={firstLessonPath}> <FullWidthRow>
Go to the first lesson <Link className='btn btn-lg btn-primary btn-block' to={firstLessonPath}>
</Link> Go to the first lesson
<ButtonSpacer /> </Link>
<Link to='/learn'> <ButtonSpacer />
<Button block={true} bsSize='lg' className='btn-primary-invert'> <Link to='/learn'>
View the curriculum <Button block={true} bsSize='lg' className='btn-primary-invert'>
</Button> View the curriculum
</Link> </Button>
<ButtonSpacer /> </Link>
<hr /> <ButtonSpacer />
</FullWidthRow> <hr />
<FullWidthRow> </FullWidthRow>
<h2 className='intro-toc-title'>Upcoming Lessons</h2> <FullWidthRow>
<ListGroup className='intro-toc'> <h2 className='intro-toc-title'>Upcoming Lessons</h2>
{allChallengeNode ? renderMenuItems(allChallengeNode) : null} <ListGroup className='intro-toc'>
</ListGroup> {allChallengeNode ? renderMenuItems(allChallengeNode) : null}
</FullWidthRow> </ListGroup>
</FullWidthRow>
</div>
</LearnLayout> </LearnLayout>
); );
} }

View File

@ -1,3 +1,9 @@
@media screen, (max-width: 767px) {
.intro-layout-container {
padding: 0 10px;
}
}
.intro-layout { .intro-layout {
margin-top: 1.45rem; margin-top: 1.45rem;
} }