The front-end UI of Coursemology is written using React.js. Most of our pages and their components are written in TypeScript as React functional components, though there are some older parts in JS or using class components that should be migrated to functional components in the future.
These commands should be run with the working directory coursemology2/client
(the same directory this README file is in)
-
Install javascript dependencies
yarn
-
Run the following command to initialize
.env
files over herecp env .env
You may need to add specific API keys (such as the GOOGLE_RECAPTCHA_SITE_KEY) to the .env files for testing specific features.
-
To start the frontend, run
yarn build:development
To generate a list of strings that need to be translated,
run the following command from the client
directory:
yarn run extract-translations
This will extract all translations from the source codes
and then combine all the keys into a single file /client/locales/en.json
.
Next, using that file as a reference, create or update other translations in the client/locales
folder.
- Prepend Immutable.js variables names with
$$
.
As of #5049, our client is transitioning to Tailwind CSS for all front-end styling. Tailwind CSS is a utility-first CSS framework for rapidly building custom user interfaces. It offers a different paradigm of traditionally writing 'semantic' CSS, allowing for definitive stylesheet consistency, ease of configuration, and better developer experience.
We strongly recommend installing Prettier, as we have integrated Tailwind's Prettier plugin to help maintain our utility class names.
Do NOT, ever, use inline styles, MUI's sx
prop, raw CSS, Sass stylesheets, styled-components
, or Emotion.
Note If you see any of these around our codebase, you may replace them with Tailwind utilities.
Use pt
or rem
as units for values. Do not use px
; it is an absolute unit. There are many articles that support this, but essentially, using relative units means we are respecting the display scaling of the browser and target device, allowing our site to be more accessible and independent of media display scaling.
Tailwind's media modifiers, e.g., sm:
, md:
, etc. are min-width
media queries. So, start your designs from small screens, then slowly work towards large screens and apply your media modifiers appropriately. This is known as the mobile-first approach, and is the usual recommendation when building responsive websites.
For brevity, keep our class names short and brief. If you have added flex
, there is no need to add flex-row
if flex-col
is not applied, because flex-direction
is row
by default. Use defaults to override non-default values. This also applies to code styling and default React props, actually.
If you find yourself battling with long and repeated utilities, consider refactoring. For example, if you find yourself duplicating ml-4
on all 6 components, consider wrapping them all in a div
and set the ml-4
there. Read Tailwind's article on Reusing Styles here.
Do not fret over 1-2 pixels and resort to arbitrary values or custom components unless necessary. The point of using Tailwind utilities is consistency across the entire app, not being pixel perfect.
There are some MUI handles that you may continue to use, for example:
<Container maxWidth="lg">
<Grid container spacing={2}>
<Grid item xs={8}>
<Typography variant="body1" color="text.secondary">
<Button color="error">
maxWidth="lg"
is legal because the breakpointlg
is defined in Tailwind and linked to MUI.spacing={2}
is legal because the value2
translates to2rem
. See MUI's Spacing.xs={8}
is legal becausexs
is a Tailwind-defined breakpoint, and8
translates to8rem
.variant="body1"
is legal because we are using Material Design's type system. In fact, always useTypography
when displaying texts. These are configurable in MUITheme
and thus Tailwind.color="error"
is legal because these colour namespaces are configurable in MUITheme
and thus Tailwind.
These built-in styling props are allowed because it makes our code more readable. Unless you are building a custom component, you should not have too many Tailwind utilities. As much as we are using Tailwind, we must also respect MUI component's inbuilt styles. If you still want to override MUI, consider using the unstyled counterpart of the component instead.
Note If all you need is a simple stack or organisation, consider
div
s instead ofGrid
s orContainer
s. Less imports, shorter codes!
❌ Refrain from using arbitrary values.
Arbitrary values allow one to supply any hardcoded value in Tailwind utilities, e.g., pb-[10px]
will apply a padding-bottom: 10px
. If we have too many of this arbitrary utilities, we are basically reverting to inline styles.
Use arbitrary values only when Tailwind does not support it, or you really need to use it for one-time styling. For example, when aligning elements to MUI Checkbox
or Radio
which have widths of exactly 34 pixels, you may use ml-[34px]
, but use it once in a div
, and put all the aligned elements in it.
If you find yourself using the same arbitrary value many times, you may consider creating a custom utility for it.
Warning Always remember that the whole point of using Tailwind is to reuse CSS attributes as utilities. If one creates a custom utility that has many CSS styles in it, are we not just reverting back to using raw CSS?
❌ Do NOT use MUI's Box
component.
The Box
component is a wrapper for short CSS utility styling and accessing the sx
prop. If you are looking for a literal box to wrap your components and apply some styles, use a div
and Tailwind utilities.
There are some exceptions, however, to using Box
. For example, when passing children props from a parent MUI component. See this example in a country select Autocomplete
where Box
received a spread props
from the renderOption
prop. This usage is legal because Box
here is used as an API operable and not (wholly) for styling.
When making reusable components, e.g., TextField
s, Checkbox
es, or Button
s alike, do not give them fixed margins or mysterious spaces.
The most common mistake is to give margin-bottom
s or padding-bottom
s to these components so that they are spaced when stacked. Instead of fixing these spaces to the reusable component, let the layout/container component set the space.
For example, instead of doing this:
<div>
<TextField label="Email address" /> <!-- ❌ TextField has className="mb-4" -->
<Button>Log in</Button> <!-- ❌ Button has className="mb-4" -->
<Link>Create a new account</Link> <!-- ❌ Link has className="mb-4" -->
</div>
do this instead:
<div className="space-y-4">
<TextField label="Email address" /> <!-- ✅ TextField defines no margins -->
<Button>Log in</Button> <!-- ✅ Button defines no margins -->
<Link>Create a new account</Link> <!-- ✅ Link defines no margins -->
</div>
This ensures consistent spacing between your components, and you no longer need to worry about the last component having a blank space.
Note Remember, a reusable component is only responsible for the space it manages, not beyond itself.
!important
is applicable to Tailwind utilities by prefixing them with !
, e.g., !ml-4
. Use this sparingly, and only for good reasons, e.g., overriding space-y-4
, Bootstrap utilities, or MUI built-in styles. Having !important
everywhere makes it hard to refactor and debug styles.