From 08c5d13926c12fe0cbd6c27646672fd23fb24b9e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 10 Sep 2024 09:51:27 +0200 Subject: [PATCH] Create new chip component to represent grade status (#6658) --- gulpfile.js | 12 +- .../components/dashboard/GradeStatusChip.tsx | 44 ++++++++ .../dashboard/test/GradeStatusChip-test.js | 58 ++++++++++ .../components/GradeStatusChipPage.tsx | 103 ++++++++++++++++++ lms/static/scripts/ui-playground/index.ts | 10 +- 5 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 lms/static/scripts/frontend_apps/components/dashboard/GradeStatusChip.tsx create mode 100644 lms/static/scripts/frontend_apps/components/dashboard/test/GradeStatusChip-test.js create mode 100644 lms/static/scripts/ui-playground/components/GradeStatusChipPage.tsx diff --git a/gulpfile.js b/gulpfile.js index d4505a6b15..41c8672ee4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -21,8 +21,8 @@ gulp.task('build-css', () => './lms/static/styles/lms.scss', './lms/static/styles/ui-playground.scss', ], - { tailwindConfig } - ) + { tailwindConfig }, + ), ); gulp.task('watch-css', () => { @@ -33,9 +33,11 @@ gulp.task('watch-css', () => { './lms/static/scripts/frontend_apps/**/*.ts', './lms/static/scripts/frontend_apps/**/*.tsx', './lms/static/scripts/ui-playground/**/*.js', + './lms/static/scripts/ui-playground/**/*.ts', + './lms/static/scripts/ui-playground/**/*.tsx', ], { ignoreInitial: false }, - gulp.series('build-css') + gulp.series('build-css'), ); }); @@ -58,6 +60,6 @@ gulp.task( karmaConfig: 'lms/static/scripts/karma.config.cjs', rollupConfig: 'rollup-tests.config.js', testsPattern: 'lms/static/scripts/**/*-test.js', - }) - ) + }), + ), ); diff --git a/lms/static/scripts/frontend_apps/components/dashboard/GradeStatusChip.tsx b/lms/static/scripts/frontend_apps/components/dashboard/GradeStatusChip.tsx new file mode 100644 index 0000000000..6ee62f16ab --- /dev/null +++ b/lms/static/scripts/frontend_apps/components/dashboard/GradeStatusChip.tsx @@ -0,0 +1,44 @@ +import classnames from 'classnames'; + +export type GradeStatusChipProps = { + /** + * A grade, from 0 to 100, that will be used to render the corresponding + * color combination. + */ + grade: number; +}; + +/** + * A badge where the corresponding color combination is calculated from a grade + * from 0 to 100, following the next table: + * + * 100 - bright green + * 80-99 - light green + * 50-79 - yellow + * 1-49 - light red + * 0 - bright red + * other - grey + */ +export default function GradeStatusChip({ grade }: GradeStatusChipProps) { + const gradeIsInvalid = grade < 0 || grade > 100; + + return ( +
= 80 && grade < 100, + 'bg-amber-100 text-amber-900': grade >= 50 && grade < 80, + 'bg-red-200 text-red-900': grade >= 1 && grade < 50, + 'bg-grey-3 text-grey-7': gradeIsInvalid, + })} + > + {grade} + {!gradeIsInvalid && '%'} +
+ ); +} diff --git a/lms/static/scripts/frontend_apps/components/dashboard/test/GradeStatusChip-test.js b/lms/static/scripts/frontend_apps/components/dashboard/test/GradeStatusChip-test.js new file mode 100644 index 0000000000..3c14513a6c --- /dev/null +++ b/lms/static/scripts/frontend_apps/components/dashboard/test/GradeStatusChip-test.js @@ -0,0 +1,58 @@ +import { checkAccessibility } from '@hypothesis/frontend-testing'; +import { mount } from 'enzyme'; + +import GradeStatusChip from '../GradeStatusChip'; + +describe('GradeStatusChip', () => { + function renderComponent(grade) { + return mount(); + } + + [0, 20, 48, 77, 92, 100].forEach(grade => { + it('renders valid grades as percentage', () => { + const wrapper = renderComponent(grade); + assert.equal(wrapper.text(), `${grade}%`); + }); + }); + + [-20, 150].forEach(grade => { + it('renders invalid grades verbatim', () => { + const wrapper = renderComponent(grade); + assert.equal(wrapper.text(), `${grade}`); + }); + }); + + it( + 'should pass a11y checks', + checkAccessibility([ + { + name: '100', + content: () => renderComponent(100), + }, + { + name: '80', + content: () => renderComponent(80), + }, + { + name: '68', + content: () => renderComponent(68), + }, + { + name: '38', + content: () => renderComponent(38), + }, + { + name: '0', + content: () => renderComponent(0), + }, + { + name: '-20', + content: () => renderComponent(-20), + }, + { + name: '150', + content: () => renderComponent(150), + }, + ]), + ); +}); diff --git a/lms/static/scripts/ui-playground/components/GradeStatusChipPage.tsx b/lms/static/scripts/ui-playground/components/GradeStatusChipPage.tsx new file mode 100644 index 0000000000..f6b8c7242e --- /dev/null +++ b/lms/static/scripts/ui-playground/components/GradeStatusChipPage.tsx @@ -0,0 +1,103 @@ +import { DataTable } from '@hypothesis/frontend-shared'; +import Library from '@hypothesis/frontend-shared/lib/pattern-library/components/Library'; + +import GradeStatusChip from '../../frontend_apps/components/dashboard/GradeStatusChip'; + +export default function GradeStatusChipPage() { + return ( + + +

+ It renders a badge with an automatically calculating color + combination, based on a grade from 0 to 100. +

+ + + + + + + + + +
+ + +

+ We plan to use the GradeStatusChip inside the dashboard + metrics tables. This is how it will look like. +

+ + { + if (field === 'grade') { + return ; + } + + return row[field]; + }} + /> + +
+
+ ); +} diff --git a/lms/static/scripts/ui-playground/index.ts b/lms/static/scripts/ui-playground/index.ts index a0299e2895..6a3b1ae3c5 100644 --- a/lms/static/scripts/ui-playground/index.ts +++ b/lms/static/scripts/ui-playground/index.ts @@ -2,8 +2,16 @@ import { startApp } from '@hypothesis/frontend-shared/lib/pattern-library'; import type { CustomPlaygroundRoute } from '@hypothesis/frontend-shared/lib/pattern-library/routes'; +import GradeStatusChipPage from './components/GradeStatusChipPage'; + // LMS prototype pages should be defined here -const extraRoutes: CustomPlaygroundRoute[] = []; +const extraRoutes: CustomPlaygroundRoute[] = [ + { + component: GradeStatusChipPage, + route: '/grade-status-chip', + title: 'Grade status chip', + }, +]; startApp({ baseURL: '/ui-playground',