style: 1.4 content and style fixes (#170)
* style: add spacing between footer and body content * fix: TNL-8567 prevent card selection when click on show features * style: TNL-8569 update page & resources margin for mobile view * fix: TNL-8571 correct error state for blockout date field * style: TNL-8569 set page&resources margins in mobile view * style: TNL-8573 feature list should intersect with column * style: TNL-8568 update font size and color for feature table * fix: move formInvalid logic to legacy form and make it centralize
This commit is contained in:
@@ -40,11 +40,17 @@ export default function CourseAuthoringPage({ courseId, children }) {
|
||||
/>
|
||||
);
|
||||
|
||||
const AppFooter = () => (
|
||||
<div className="mt-6">
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-light-200">
|
||||
{inProgress ? <Loading /> : AppHeader()}
|
||||
{inProgress ? <Loading /> : <AppHeader />}
|
||||
{children}
|
||||
{!inProgress && <Footer />}
|
||||
{!inProgress && <AppFooter />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ function PagesAndResources({ courseId, intl }) {
|
||||
|
||||
return (
|
||||
<PagesAndResourcesProvider courseId={courseId}>
|
||||
<main className="container container-mw-md">
|
||||
<div className="d-flex justify-content-between my-5 align-items-center">
|
||||
<main className="container container-mw-md px-3">
|
||||
<div className="d-flex justify-content-between my-4 my-md-5 align-items-center">
|
||||
<h3 className="m-0">{intl.formatMessage(messages.heading)}</h3>
|
||||
<Hyperlink
|
||||
destination={lmsCourseURL}
|
||||
|
||||
@@ -65,8 +65,8 @@ function AppSettingsModal({
|
||||
const appInfo = useModel('courseApps', appId);
|
||||
const dispatch = useDispatch();
|
||||
const submitButtonState = updateSettingsRequestStatus === RequestStatus.IN_PROGRESS ? 'pending' : 'default';
|
||||
const isTabletOrMobile = useIsMobile();
|
||||
const modalVariant = isTabletOrMobile ? 'dark' : 'default';
|
||||
const isMobile = useIsMobile();
|
||||
const modalVariant = isMobile ? 'dark' : 'default';
|
||||
|
||||
useEffect(() => {
|
||||
if (updateSettingsRequestStatus === RequestStatus.SUCCESSFUL) {
|
||||
@@ -102,7 +102,7 @@ function AppSettingsModal({
|
||||
isOpen
|
||||
closeText={intl.formatMessage(messages.cancel)}
|
||||
dialogClassName="modal-dialog-centered modal-lg"
|
||||
hasCloseButton={isTabletOrMobile}
|
||||
hasCloseButton={isMobile}
|
||||
onClose={onClose}
|
||||
variant={modalVariant}
|
||||
isFullscreenOnMobile
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Card, Form } from '@edx/paragon';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Formik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
@@ -10,6 +11,7 @@ import AnonymousPostingFields from '../shared/AnonymousPostingFields';
|
||||
import DiscussionTopics from '../shared/discussion-topics/DiscussionTopics';
|
||||
import BlackoutDatesField, { blackoutDatesRegex } from '../shared/BlackoutDatesField';
|
||||
import LegacyConfigFormProvider from './LegacyConfigFormProvider';
|
||||
import { updateValidationStatus } from '../../../data/slice';
|
||||
|
||||
import messages from '../shared/messages';
|
||||
import AppConfigFormDivider from '../shared/AppConfigFormDivider';
|
||||
@@ -37,6 +39,13 @@ Yup.addMethod(Yup.object, 'uniqueProperty', function (propertyName, message) {
|
||||
function LegacyConfigForm({
|
||||
appConfig, onSubmit, formRef, intl, title,
|
||||
}) {
|
||||
const [isFormInvalid, setIsFormInvalid] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(updateValidationStatus({ hasError: isFormInvalid }));
|
||||
}, [isFormInvalid]);
|
||||
|
||||
const [validDiscussionTopics, setValidDiscussionTopics] = useState(appConfig.discussionTopics);
|
||||
const legacyFormValidationSchema = Yup.object().shape({
|
||||
blackoutDates: Yup.string().matches(
|
||||
@@ -79,6 +88,8 @@ function LegacyConfigForm({
|
||||
setValidDiscussionTopics,
|
||||
discussionTopicErrors,
|
||||
};
|
||||
setIsFormInvalid(discussionTopicErrors.some((error) => error === true)
|
||||
|| Boolean(touched.blackoutDates && errors.blackoutDates));
|
||||
|
||||
return (
|
||||
<LegacyConfigFormProvider value={contextValue}>
|
||||
@@ -96,12 +107,7 @@ function LegacyConfigForm({
|
||||
<AppConfigFormDivider thick />
|
||||
<DivisionByGroupFields />
|
||||
<AppConfigFormDivider thick />
|
||||
<BlackoutDatesField
|
||||
errors={errors}
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
values={values}
|
||||
/>
|
||||
<BlackoutDatesField />
|
||||
</Form>
|
||||
</Card>
|
||||
</LegacyConfigFormProvider>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useState } from 'react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Form } from '@edx/paragon';
|
||||
import { Form, TransitionReplace } from '@edx/paragon';
|
||||
import { useFormikContext } from 'formik';
|
||||
import messages from './messages';
|
||||
|
||||
/**
|
||||
@@ -68,49 +68,57 @@ import messages from './messages';
|
||||
*/
|
||||
export const blackoutDatesRegex = /^\[(\[("[0-9]{4}-(0[1-9]|1[0-2])-[0-3][0-9](T([0-1][0-9]|2[0-3]):([0-5][0-9])){0,1}"),("[0-9]{4}-(0[1-9]|1[0-2])-[0-3][0-9](T([0-1][0-9]|2[0-3]):([0-5][0-9])){0,1}")\](,){0,1})*\]$/;
|
||||
|
||||
function BlackoutDatesField({
|
||||
onBlur,
|
||||
onChange,
|
||||
intl,
|
||||
values,
|
||||
errors,
|
||||
}) {
|
||||
const BlackoutDatesField = ({ intl }) => {
|
||||
const [inFocus, setInFocus] = useState(false);
|
||||
const {
|
||||
handleChange, handleBlur, errors,
|
||||
touched, values: appConfig,
|
||||
} = useFormikContext();
|
||||
|
||||
const hasError = Boolean(touched.blackoutDates && errors.blackoutDates);
|
||||
|
||||
const handleFocusOut = (event) => {
|
||||
handleBlur(event);
|
||||
setInFocus(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h5 className="my-4 text-gray-500">{intl.formatMessage(messages.blackoutDates)}</h5>
|
||||
<Form.Group
|
||||
controlId="blackoutDates"
|
||||
isInvalid={hasError && !inFocus}
|
||||
className="m-2"
|
||||
>
|
||||
<Form.Control
|
||||
value={values.blackoutDates}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
className="mb-3"
|
||||
value={appConfig.blackoutDates}
|
||||
onChange={handleChange}
|
||||
onBlur={(event) => handleFocusOut(event)}
|
||||
className="mb-1"
|
||||
floatingLabel={intl.formatMessage(messages.blackoutDatesLabel)}
|
||||
onFocus={() => setInFocus(true)}
|
||||
/>
|
||||
{errors.blackoutDates && (
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors.blackoutDates}
|
||||
</Form.Control.Feedback>
|
||||
)}
|
||||
<Form.Text muted>
|
||||
<TransitionReplace key="blackoutDates">
|
||||
{hasError && !inFocus ? (
|
||||
<React.Fragment key="open">
|
||||
<Form.Control.Feedback type="invalid" hasIcon={false}>
|
||||
<div className="small">{errors.blackoutDates}</div>
|
||||
</Form.Control.Feedback>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment key="closed" />
|
||||
)}
|
||||
</TransitionReplace>
|
||||
<Form.Text muted className="mt-3">
|
||||
{intl.formatMessage(messages.blackoutDatesHelp)}
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
BlackoutDatesField.propTypes = {
|
||||
onBlur: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
values: PropTypes.shape({
|
||||
blackoutDates: PropTypes.string,
|
||||
}).isRequired,
|
||||
errors: PropTypes.shape({
|
||||
blackoutDates: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BlackoutDatesField);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useContext, useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import React, { useContext, useCallback } from 'react';
|
||||
import { Add } from '@edx/paragon/icons';
|
||||
import { Button } from '@edx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
@@ -8,7 +7,6 @@ import { v4 as uuid } from 'uuid';
|
||||
import _ from 'lodash';
|
||||
import messages from '../messages';
|
||||
import TopicItem from './TopicItem';
|
||||
import { updateValidationStatus } from '../../../../data/slice';
|
||||
import { LegacyConfigFormContext } from '../../legacy/LegacyConfigFormProvider';
|
||||
import filterItemFromObject from '../../../utils';
|
||||
|
||||
@@ -19,18 +17,12 @@ const DiscussionTopics = ({ intl }) => {
|
||||
setFieldValue,
|
||||
} = useFormikContext();
|
||||
const { discussionTopics, divideDiscussionIds } = appConfig;
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
discussionTopicErrors,
|
||||
validDiscussionTopics,
|
||||
setValidDiscussionTopics,
|
||||
} = useContext(LegacyConfigFormContext);
|
||||
|
||||
const isFormInvalid = discussionTopicErrors.some((error) => error === true);
|
||||
useEffect(() => {
|
||||
dispatch(updateValidationStatus({ hasError: isFormInvalid }));
|
||||
}, [isFormInvalid]);
|
||||
|
||||
const handleTopicDelete = async (topicIndex, topicId, remove) => {
|
||||
await remove(topicIndex);
|
||||
validateForm();
|
||||
|
||||
@@ -45,7 +45,7 @@ function AppCard({
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<Card.Body className="m-2">
|
||||
<Card.Body>
|
||||
<div className="h4 card-title">
|
||||
{intl.formatMessage(messages[`appName-${app.id}`])}
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@ const NonSupportedFeature = (
|
||||
function FeaturesList({ app, features, intl }) {
|
||||
return (
|
||||
<Collapsible
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
title={(
|
||||
<>
|
||||
<Collapsible.Visible whenClosed>
|
||||
@@ -32,7 +33,7 @@ function FeaturesList({ app, features, intl }) {
|
||||
styling="basic"
|
||||
>
|
||||
{features && features.map((feature) => (
|
||||
<div key={`collapsible-${app.id}&${feature.id}`}>
|
||||
<div key={`collapsible-${app.id}&${feature.id}`} className="d-flex mb-1">
|
||||
{app.featureIds.includes(feature.id)
|
||||
? SupportedFeature
|
||||
: NonSupportedFeature}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Remove, Check } from '@edx/paragon/icons';
|
||||
import { DataTable } from '@edx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import messages from './messages';
|
||||
import './FeaturesTable.scss';
|
||||
|
||||
function FeaturesTable({ apps, features, intl }) {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
table {
|
||||
font-size: 14px;
|
||||
color: black;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { history } from '@edx/frontend-platform';
|
||||
import classNames from 'classnames';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Badge, Card, Icon, IconButton, Hyperlink,
|
||||
@@ -8,6 +9,8 @@ import PropTypes from 'prop-types';
|
||||
import React, { useContext } from 'react';
|
||||
import messages from '../messages';
|
||||
import { PagesAndResourcesContext } from '../PagesAndResourcesProvider';
|
||||
import { useIsDesktop } from '../../utils';
|
||||
import './PageCard.scss';
|
||||
|
||||
const CoursePageShape = PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
@@ -27,6 +30,7 @@ function PageCard({
|
||||
page,
|
||||
}) {
|
||||
const { path: pagesAndResourcesPath } = useContext(PagesAndResourcesContext);
|
||||
const isDesktop = useIsDesktop();
|
||||
|
||||
const SettingsButton = () => {
|
||||
if (page.legacyLink) {
|
||||
@@ -55,11 +59,13 @@ function PageCard({
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="shadow card"
|
||||
style={{
|
||||
width: '19rem',
|
||||
height: '14rem',
|
||||
}}
|
||||
className={classNames(
|
||||
'shadow card',
|
||||
{
|
||||
'desktop-card': isDesktop,
|
||||
'mobile-card': !isDesktop,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Card.Body className="d-flex flex-column justify-content-between">
|
||||
<Card.Title className="d-flex mb-0 align-items-center justify-content-between">
|
||||
|
||||
9
src/pages-and-resources/pages/PageCard.scss
Normal file
9
src/pages-and-resources/pages/PageCard.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
.desktop-card {
|
||||
width: 19rem;
|
||||
height: 14rem;
|
||||
}
|
||||
|
||||
.mobile-card {
|
||||
width: 100%;
|
||||
height: 14rem;
|
||||
}
|
||||
@@ -6,5 +6,9 @@ export const executeThunk = async (thunk, dispatch, getState) => {
|
||||
};
|
||||
|
||||
export function useIsMobile() {
|
||||
return useMediaQuery({ query: '(max-width: 768px)' });
|
||||
return useMediaQuery({ query: '(max-width: 767.98px)' });
|
||||
}
|
||||
|
||||
export function useIsDesktop() {
|
||||
return useMediaQuery({ query: '(min-width: 992px)' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user