Skip to content

Commit ee19008

Browse files
Ahtesham QuraishAhtesham Quraish
authored andcommitted
feat: settings design for section, subsection and unit
1 parent 97a805d commit ee19008

File tree

4 files changed

+346
-1
lines changed

4 files changed

+346
-1
lines changed

src/library-authoring/containers/ContainerInfo.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from '../common/context/SidebarContext';
2525
import ContainerOrganize from './ContainerOrganize';
2626
import ContainerUsage from './ContainerUsage';
27+
import { SettingsPanel } from './SettingsPanel';
2728
import { useLibraryRoutes } from '../routes';
2829
import { LibraryUnitBlocks } from '../units/LibraryUnitBlocks';
2930
import { LibraryContainerChildren } from '../section-subsections/LibraryContainerChildren';
@@ -225,7 +226,7 @@ const ContainerInfo = () => {
225226
<ContainerUsage />
226227
</Tab.Pane>
227228
<Tab.Pane eventKey={CONTAINER_INFO_TABS.Settings}>
228-
{/* TODO: Container settings component */}
229+
<SettingsPanel containerType={containerType} />
229230
</Tab.Pane>
230231
</Tab.Content>
231232
</Tab.Container>
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { IntlProvider } from '@edx/frontend-platform/i18n';
4+
import { ContainerType } from '@src/generic/key-utils';
5+
import { SettingsPanel } from './SettingsPanel';
6+
import messages from './messages';
7+
8+
const renderWithIntl = (ui: React.ReactNode) => render(
9+
<IntlProvider locale="en" messages={{}}>
10+
{ui}
11+
</IntlProvider>,
12+
);
13+
14+
describe('SettingsPanel', () => {
15+
describe('Section container', () => {
16+
test('renders section default info text', () => {
17+
renderWithIntl(<SettingsPanel containerType={ContainerType.Section} />);
18+
expect(
19+
screen.getByText(messages.settingsSectionDefaultText.defaultMessage),
20+
).toBeInTheDocument();
21+
});
22+
23+
test('does not render grading or results visibility', () => {
24+
renderWithIntl(<SettingsPanel containerType={ContainerType.Section} />);
25+
expect(
26+
screen.queryByText(messages.settingsSectionGradingLabel.defaultMessage),
27+
).not.toBeInTheDocument();
28+
expect(
29+
screen.queryByText(
30+
messages.settingsSectionAssessmentResultsVisibilityLabel.defaultMessage,
31+
),
32+
).not.toBeInTheDocument();
33+
});
34+
35+
test('renders visibility controls', () => {
36+
renderWithIntl(<SettingsPanel containerType={ContainerType.Section} />);
37+
expect(
38+
screen.getByText(messages.settingsSectionVisibilityLabel.defaultMessage),
39+
).toBeInTheDocument();
40+
expect(
41+
screen.getByRole('button', {
42+
name: messages.settingsSectionDefaultVisibilityButton.defaultMessage,
43+
}),
44+
).toBeDisabled();
45+
});
46+
});
47+
48+
describe('Subsection container', () => {
49+
test('renders subsection default info text', () => {
50+
renderWithIntl(<SettingsPanel containerType={ContainerType.Subsection} />);
51+
expect(
52+
screen.getByText(messages.settingsSubSectionDefaultText.defaultMessage),
53+
).toBeInTheDocument();
54+
});
55+
56+
test('renders grading buttons (disabled)', () => {
57+
renderWithIntl(<SettingsPanel containerType={ContainerType.Subsection} />);
58+
expect(
59+
screen.getByRole('button', {
60+
name: messages.settingsSectionUpgradeButton.defaultMessage,
61+
}),
62+
).toBeDisabled();
63+
expect(
64+
screen.getByRole('button', {
65+
name: messages.settingsSectionGradeButton.defaultMessage,
66+
}),
67+
).toBeDisabled();
68+
});
69+
70+
test('renders visibility + hide content checkbox', () => {
71+
renderWithIntl(<SettingsPanel containerType={ContainerType.Subsection} />);
72+
expect(
73+
screen.getByLabelText(
74+
messages.settingsSectionHideContentAfterDueDateLabel.defaultMessage,
75+
),
76+
).toBeDisabled();
77+
});
78+
79+
test('renders results visibility controls', () => {
80+
renderWithIntl(<SettingsPanel containerType={ContainerType.Subsection} />);
81+
expect(
82+
screen.getByRole('button', {
83+
name: messages.settingsSectionShowButton.defaultMessage,
84+
}),
85+
).toBeDisabled();
86+
expect(
87+
screen.getByRole('button', {
88+
name: messages.settingsSectionHideButton.defaultMessage,
89+
}),
90+
).toBeDisabled();
91+
expect(
92+
screen.getByLabelText(
93+
messages.settingsSectionOnlyShowResultsAfterDueDateLabel.defaultMessage,
94+
),
95+
).toBeDisabled();
96+
});
97+
});
98+
99+
describe('Unit container', () => {
100+
test('renders unit default info text', () => {
101+
renderWithIntl(<SettingsPanel containerType={ContainerType.Unit} />);
102+
expect(
103+
screen.getByText(messages.settingsUnitDefaultText.defaultMessage),
104+
).toBeInTheDocument();
105+
});
106+
107+
test('renders discussion settings', () => {
108+
renderWithIntl(<SettingsPanel containerType={ContainerType.Unit} />);
109+
expect(
110+
screen.getByText(messages.settingsSectionDiscussionLabel.defaultMessage),
111+
).toBeInTheDocument();
112+
expect(
113+
screen.getByLabelText(
114+
messages.settingsSectionEnableDiscussionLabel.defaultMessage,
115+
),
116+
).toBeChecked();
117+
expect(
118+
screen.getByText(messages.settingsSectionUnpublishedUnitsLabel.defaultMessage),
119+
).toBeInTheDocument();
120+
});
121+
});
122+
});
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import React, { useState } from 'react';
2+
import { FormattedMessage } from '@edx/frontend-platform/i18n';
3+
import { Button, ButtonGroup, Form } from '@openedx/paragon';
4+
import { ContainerType } from '@src/generic/key-utils';
5+
import messages from './messages';
6+
7+
interface SettingsPanelProps {
8+
containerType: string;
9+
}
10+
11+
export const SettingsPanel: React.FC<SettingsPanelProps> = ({ containerType }) => {
12+
const [grading, setGrading] = useState('ungraded');
13+
const [visibility, setVisibility] = useState('default');
14+
const [resultsVisibility, setResultsVisibility] = useState('show');
15+
16+
const disableAll = true; // 👈 set to false to re-enable
17+
18+
return (
19+
<>
20+
<div className="pb-2 pl-4 pr-4 space-y-4">
21+
<p className="text-muted small mb-4">
22+
{ containerType === ContainerType.Section && (
23+
<FormattedMessage {...messages.settingsSectionDefaultText} />
24+
)}
25+
{ containerType === ContainerType.Subsection && (
26+
<FormattedMessage {...messages.settingsSubSectionDefaultText} />
27+
)}
28+
{ containerType === ContainerType.Unit && (
29+
<FormattedMessage {...messages.settingsUnitDefaultText} />
30+
)}
31+
</p>
32+
</div>
33+
<div className="pb-4 pl-4 pr-4 space-y-4">
34+
{containerType === ContainerType.Subsection && (
35+
<>
36+
<h6 className="text-muted small font-weight-bold mb-3">
37+
<FormattedMessage {...messages.settingsSectionGradingLabel} />
38+
</h6>
39+
<ButtonGroup className="d-flex w-100 mb-4.5">
40+
<Button
41+
className="flex-fill"
42+
variant={grading === 'ungraded' ? 'dark' : 'outline-secondary'}
43+
onClick={() => setGrading('ungraded')}
44+
size="sm"
45+
disabled={disableAll}
46+
>
47+
<FormattedMessage {...messages.settingsSectionUpgradeButton} />
48+
</Button>
49+
<Button
50+
className="flex-fill"
51+
variant={grading === 'graded' ? 'dark' : 'outline-secondary'}
52+
onClick={() => setGrading('graded')}
53+
size="sm"
54+
disabled={disableAll}
55+
>
56+
<FormattedMessage {...messages.settingsSectionGradeButton} />
57+
</Button>
58+
</ButtonGroup>
59+
</>
60+
)}
61+
62+
<h6 className="text-muted small font-weight-bold mt-3 mb-3">
63+
<FormattedMessage {...messages.settingsSectionVisibilityLabel} />
64+
</h6>
65+
<ButtonGroup className="d-flex w-100">
66+
<Button
67+
className="flex-fill"
68+
variant={visibility === 'default' ? 'dark' : 'outline-secondary'}
69+
onClick={() => setVisibility('default')}
70+
size="sm"
71+
disabled={disableAll}
72+
>
73+
<FormattedMessage {...messages.settingsSectionDefaultVisibilityButton} />
74+
</Button>
75+
<Button
76+
className="flex-fill"
77+
variant={visibility === 'staff' ? 'dark' : 'outline-secondary'}
78+
onClick={() => setVisibility('staff')}
79+
size="sm"
80+
disabled={disableAll}
81+
>
82+
<FormattedMessage {...messages.settingsSectionStaffOnlyButton} />
83+
</Button>
84+
</ButtonGroup>
85+
{containerType === ContainerType.Subsection && (
86+
<Form.Checkbox className="mt-3 text-muted mb-4.5" disabled>
87+
<FormattedMessage {...messages.settingsSectionHideContentAfterDueDateLabel} />
88+
</Form.Checkbox>
89+
)}
90+
91+
{containerType === ContainerType.Subsection && (
92+
<>
93+
<h6 className="text-muted small font-weight-bold mt-1 mb-3">
94+
<FormattedMessage {...messages.settingsSectionAssessmentResultsVisibilityLabel} />
95+
</h6>
96+
<ButtonGroup className="d-flex w-100">
97+
<Button
98+
className="flex-fill"
99+
variant={resultsVisibility === 'show' ? 'dark' : 'outline-secondary'}
100+
onClick={() => setResultsVisibility('show')}
101+
size="sm"
102+
disabled={disableAll}
103+
>
104+
<FormattedMessage {...messages.settingsSectionShowButton} />
105+
</Button>
106+
<Button
107+
className="flex-fill"
108+
variant={resultsVisibility === 'hide' ? 'dark' : 'outline-secondary'}
109+
onClick={() => setResultsVisibility('hide')}
110+
size="sm"
111+
disabled={disableAll}
112+
>
113+
<FormattedMessage {...messages.settingsSectionHideButton} />
114+
</Button>
115+
</ButtonGroup>
116+
<Form.Checkbox className="mt-3 mb-4.5" disabled={disableAll}>
117+
<FormattedMessage {...messages.settingsSectionOnlyShowResultsAfterDueDateLabel} />
118+
</Form.Checkbox>
119+
</>
120+
)}
121+
{containerType === ContainerType.Unit && (
122+
<>
123+
<h6 className="text-muted small font-weight-bold mt-3 mb-3">
124+
<FormattedMessage {...messages.settingsSectionDiscussionLabel} />
125+
</h6>
126+
<Form.Checkbox className="mt-3" checked>
127+
<FormattedMessage {...messages.settingsSectionEnableDiscussionLabel} />
128+
</Form.Checkbox>
129+
<p className="text-muted small mb-4">
130+
<FormattedMessage {...messages.settingsSectionUnpublishedUnitsLabel} />
131+
</p>
132+
</>
133+
)}
134+
</div>
135+
</>
136+
);
137+
};

src/library-authoring/containers/messages.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,91 @@ const messages = defineMessages({
216216
defaultMessage: 'Failed to publish changes',
217217
description: 'Popup text seen if publishing a container fails',
218218
},
219+
settingsSectionDefaultText: {
220+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-default-text',
221+
defaultMessage: 'Section settings can not be configured within Libraries and must be set within a course. In a future release, Libraries may support configuring some settings.',
222+
description: 'Settings section tab default text',
223+
},
224+
settingsSubSectionDefaultText: {
225+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-subsection-default-text',
226+
defaultMessage: 'Subsection settings can not be configured within Libraries and must be set within a course. In a future release, Libraries may support configuring some settings.',
227+
description: 'Settings subsection tab default text',
228+
},
229+
settingsUnitDefaultText: {
230+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-unit-default-text',
231+
defaultMessage: 'This unit was imported from a course. When you reuse this section, you can configure the settings locally. In a future release, Content Libraries may support configuring some settings.',
232+
description: 'Settings unit tab default text',
233+
},
234+
settingsSectionGradingLabel: {
235+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-grading-label',
236+
defaultMessage: 'Subsection Grading',
237+
description: 'Label for the grading section in settings',
238+
},
239+
settingsSectionVisibilityLabel: {
240+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-visibility-label',
241+
defaultMessage: 'Visibility',
242+
description: 'Label for the visibility in settings',
243+
},
244+
settingsSectionAssessmentResultsVisibilityLabel: {
245+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-assessment-results-visibility-label',
246+
defaultMessage: 'Assessment Results Visibility',
247+
description: 'Label for the Assessment Results Visibility in settings',
248+
},
249+
settingsSectionUpgradeButton: {
250+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-upgrade-button',
251+
defaultMessage: 'Upgrade',
252+
description: 'Label for the upgrade button in settings',
253+
},
254+
settingsSectionGradeButton: {
255+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-grade-button',
256+
defaultMessage: 'Grade',
257+
description: 'Label for the grade button in settings',
258+
},
259+
settingsSectionDefaultVisibilityButton: {
260+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-default-visibility-button',
261+
defaultMessage: 'Default Visibility',
262+
description: 'Label for the default visibility button in settings',
263+
},
264+
settingsSectionStaffOnlyButton: {
265+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-staff-only-button',
266+
defaultMessage: 'Staff Only',
267+
description: 'Label for the staff only button in settings',
268+
},
269+
settingsSectionShowButton: {
270+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-show-button',
271+
defaultMessage: 'Show',
272+
description: 'Label for the show button in settings',
273+
},
274+
settingsSectionHideButton: {
275+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-hide-button',
276+
defaultMessage: 'Hide',
277+
description: 'Label for the hide button in settings',
278+
},
279+
settingsSectionHideContentAfterDueDateLabel: {
280+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-hide-content-after-due-date',
281+
defaultMessage: 'Hide content after due date',
282+
description: 'Label for the hide content after due date checkbox in settings',
283+
},
284+
settingsSectionOnlyShowResultsAfterDueDateLabel: {
285+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-only-show-results-after-due-date',
286+
defaultMessage: 'Only show results after due date',
287+
description: 'Label for the only show results after due date checkbox in settings',
288+
},
289+
settingsSectionDiscussionLabel: {
290+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-discussion-label',
291+
defaultMessage: 'Discussion',
292+
description: 'Label for the discussion in settings',
293+
},
294+
settingsSectionEnableDiscussionLabel: {
295+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-enable-discussion-label',
296+
defaultMessage: 'Enable Discussion',
297+
description: 'Label for the enable discussion checkbox in settings',
298+
},
299+
settingsSectionUnpublishedUnitsLabel: {
300+
id: 'course-authoring.library-authoring.container-sidebar.publisher.settings-section-unpublished-units-label',
301+
defaultMessage: 'Topics for unpublished units will not be created',
302+
description: 'Label for the unpublished units in settings',
303+
},
219304
});
220305

221306
export default messages;

0 commit comments

Comments
 (0)