9.16.0 • Published 7 days ago

@opengeoweb/theme v9.16.0

Weekly downloads
140
License
Apache-2.0
Repository
gitlab
Last release
7 days ago

Theme

The aim of this library is to make sure all MUI components look as specified in the designs found on Zeplin. There are multiple themes (dark and light) with color and styling which can be added and all values are accessible throughout the whole application.

Content

Table of contents generated with markdown-toc

Quick start

Running storybook

nx storybook theme

Adding styles/colors

  1. Add the name and type on the GeowebColorPalette type
// libs/theme/src/lib/components/Theme/types.ts
export type GeowebColorPalette = {
  buttons: {
    primaryMouseOver: CSSProperties,
  },
  // ... rest of type
};
  1. Add the style/color values in lightTheme and darkTheme
/// libs/theme/src/lib/components/Theme/lightTheme.ts
export const colors: GeowebColorPalette = {
  buttons: {
    primaryMouseOver: {
      fill: '#186DFF',
      border: '#71A6FF',
    },
  },
  1. Verify it works by navigating to the Color story demo and see the new colors work in all themes. Every property you fill in is accessible throughout every app or library.

Using styles/colors to (React/MUI) components

  1. Make sure the theme ThemeProvider is used
import { ThemeProvider } from '@opengeoweb/theme';

export const Wrapper: React.FC = () => (
  <ThemeProvider>
    <MyComponent />
  </ThemeProvider>
);

note make sure to use the ThemeProvider in unit tests too, as they will fail otherwise

  1. Use the style/color in your component. You can do this directly by passing an style object as sx props or create an object with styles which could be handy if you need to reuse styles. The theme palette can be found as geowebColor property inside the theme.palette of the MUI Theme
const styles = {
  button: {
    border: 'geowebColors.buttons.flatMouseOver.border',
    '&:hover': {
      backgroundColor: 'geowebColors.buttons.flatMouseOver.fill',
    },
  },
};

const MyButton: React.FC = () => {
  return <Button sx={styles.button}>content</Button>;
};

Using styles/colors in canvas components

Since the color values are depending on the current selected theme, we can't use a static object for color values. A simple solution for this is to access the theme via useTheme hook selector, and pass it as a param to the canvas drawing function.

  1. Use useTheme selector and pass theme as argument to the draw method:
// libs/core/src/lib/components/TimeSlider/TimeSliderLegend/TimeSliderLegend.tsx
const TimeSliderLegend: React.FC<TimeSliderLegendProps> = (
  props: TimeSliderLegendProps,
) => {
  const theme = useTheme();
  return (
    <div>
      <CanvasComponent
        onRenderCanvas={(ctx: CanvasRenderingContext2D): void => {
          renderTimeSliderLegend(
            ctx,
            theme,
            // ...props and other methods
          );
        }}
      />
    </div>
  );
};
  1. Extend the render method with theme as argument, and pass if necessary the theme further to the other render methods. Most easy way is to just pass the whole theme as object, so you can use the Theme type from MUI.
// libs/core/src/lib/components/TimeSlider/TimeSliderLegend/TimeSliderLegendRenderFunctions.tsx
import { Theme } from  '@mui/material';

export const renderTimeSliderLegend = (
  context: CanvasRenderingContext2D,
  theme: Theme,
  canvasWidth: number,
  height: number,
  centerTime: number,
  secondsPerPx: number,
  selectedTimeUnix: number,
  scale: Scale,
  currentTimeUnix: number,
): void => {
  const ctx = context;

  drawBackground(
    ctx,
    theme,
    visibleTimeStart,
    visibleTimeEnd,
    canvasWidth,
    height,
    scale,
  );
  1. Use the color (or any other given property) by deconstructing the passed theme if needed, and if needed use the rgba property.
// libs/core/src/lib/components/TimeSlider/TimeSliderLegend/TimeSliderLegendRenderFunctions.tsx
const drawBackground = (
  context: CanvasRenderingContext2D,
  theme: Theme,
  visibleTimeStart: number,
  visibleTimeEnd: number,
  canvasWidth: number,
  height: number,
  scale: Scale,
): void => {
  const ctx = context;
  const { timelineTimelineSurface, timelineNightTime} = theme.palette.geowebColors.timeSlider

    ctx.fillStyle = isColorIntervalEven(scale, timestep)
      ? timelineTimelineSurface.rgba
      : timelineNightTime.rgba;
  });
};

Using themes in stories

When using a component which relies on theme styling, wrap the main component of your story with the ThemeWrapper component instead of ThemeProvider. ThemeWrapper will later get an extra StyleEngineProvider parent component (see https://mui.com/guides/migration-v4/#style-library).

If you need to show multiple stories and don't want to see the default background, use the disableCssBaseline property to prevent that.

import { darkTheme, ThemeWrapper } from '../../components/Theme';

export const TableDark = (): React.ReactElement => (
  <ThemeWrapper theme={darkTheme}>
    <TableDemo />
  </ThemeWrapper>
);

Rules of theme lib

  • It should not export any other component than the themes, ThemeProvider and corresponding hooks and the icons. This lib is only showing the MUI components and icons with the correct default style. If you need a component which needs custom props and should be used across different applications, consider making a new component in the shared lib.
  • It should not import any library other than MUI.

Folder structure

// lib/components
// lib/components/Icons/Icons.tsx
// lib/components/Theme/darkTheme.ts
// lib/components/Theme/lighTheme.ts
// lib/components/Theme/utils.ts
// lib/components/Theme/types.ts
// lib/components/Theme/ThemeContext.tsx

// lib/stories
// lib/stories/StoryWrapper.tsx
// lib/stories/story.stories.tsx

Icons (lib/components/Icons/Icons.tsx)

All icons are defined here.

types (lib/components/Theme/types.ts)

Defines the color palette of the theme.

themes (lib/components/Theme/darkTheme.ts, .../lightTheme.ts)

Exports files containing all the values per theme.

ThemeContext (lib/components/Theme/ThemeContext)

  • exports ThemeProvider wrapper for all MUI components including a CssBaseline component of MUI.
  • exports an useThemeContext to switch themes.
  • exports ThemeWrapper wrapper for stories

utils (lib/components/Theme/utils.ts)

  • hex2rgba handles hex values to rgba
  • parseColors parses the theme values and when opacity and fill are given, adds a rgba value. This is handy for canvas components, as they expect one value to render a fill.

    If in future another parser is needed (for example a font parser: when fontSize and fontFamily is given, return font which combines those values), it can be added here,

  • createShadows creates a list of shadows (elevations) as specified by design . These can be accessed through the theming by theme.shadows.

  • createTheme function that creates the theme with given colors and shadows. This also override default MUI components. It sets the values of the theme as geowebColors inside the palette of the MUI Theme.

stories (lib/stories)

Contains demos of MUI components within the theme. More information

Naming convention

When adding a new color, it needs to follow the names provided by design. It follows the pattern segmentName.elementName.value or segmentName.elementName.elementProp1.value.

naming convention

If you look at the example above, you can see two segments: Background and Buttons. Backgrounds don't need much properties other then fill, so we don't need deep value. Buttons on the other hand have next to a fill a border, so it makes sense to specify it a bit more:

// lib/components/Theme/lightTheme.ts
  background: {
    surface: '#FFFFFF',
    surfaceApp: '#F5F5F5',
    surfaceBrowser: '#CFCFCF',
  },
  buttons: {
    primary: {
      fill: '#F186DFF',
      border: 'none',
    },
    tertiary: {
      fill: 'none',
      border: '#0075A9',
    }
  }

Some Components have more colors sections. Take a look at some of the colors of the Timeslider. enter image description here

Note: Some color names have (D) after their name (currently only in Dark theme). This means the color already defined in the main Color Palette and you don't have to define it again.

The segment is TimeSlider, and a sub segment is Player and Time span. We don't want to create another level of depth so we solve this by adding this 'sub segment' as a prefix to the name of the element.

// lib/components/Theme/darkTheme.ts
 timeSlider: {
    playerNeedlePlayerTop: {
      fill: '#E3004F',
      opacity: 100,
    },
	timeScaleText: {
      fontSize: 12,
    },
    timeScaleTimeIndicatiors: {
      fill: '#A2A2A2',
    },
  },

Note: fill is often used for elements but color for example is also possible for font elements

If there are mistakes in naming, colors are missing in the Design, contact Didier. Other way around as well, if any names have been changed Didier should create a ticket to fix it here as well to keep consistency.

Theme Stories (lib/stories)

Contains demo stories with MUI components. Every story is build with the StoryWrapper component and can be toggled from light to dark theme. It does not export anything, it only shows the MUI components in light or dark theme.

  • The Color story shows all the colors, styles and other properties of the defined theme. Could be handy for reviewing styling values. On the right the geowebColor object is shown, and these values are accessible throughout all components with the ThemeProvider wrapper. Not that more values are shown than given, this is because of the parsers that adds in some cases extra properties as for example rgba
  • The Elevation shows all elevations we currently have. These can be used for defining box-shadow for elements. If you want to use the elevation in code:
const styles = {
  header: {
    boxShadow: 1, // elevation_01
  },
};
  • The rest of the stories are for showing the MUI elements without any styling, other then the given theme styling.

Overwrite default MUI component styling

When developing and using a new component of the MUI library which has no theme story, it could be a good idea to create a story, add the component there, and add some theme styling. That way there is a clear example how the component will look in the MUI environment.

In the function below createTheme takes the value of theme and shadows (elevation) and creates the theme. Some default components are overwritten as MuiCssBaseline and MuiRadio with given theme styling.

// libs/theme/src/lib/components/Theme/utils.ts
export const createTheme = (
  paletteType: PaletteType,
  geowebColors: GeowebColorPalette,
  shadows: Shadows,
): Theme =>
  createMuiTheme({
    palette: {
      background: {
        paper: geowebColors.background.surfaceApp,
        default: geowebColors.background.surfaceBrowser,
      },
      text: {
        primary: geowebColors.typographyAndIcons.text,
      },
      // geoweb color palette
      geowebColors,
    },
    shape: { borderRadius: BORDER_RADIUS },
    typography: {
      fontFamily: ['Roboto', 'Helvetica', 'Arial', 'sans-serif'].join(','),
    },
    shadows,
    overrides: {
      MuiCssBaseline: {
        '@global': {
          body: {
            fontSmoothing: 'auto',
          },
        },
      },
      MuiRadio: {
        root: {
          color: geowebColors.typographyAndIcons.iconLinkActive,
        },
      },
    }

Note: when you add or change a style of a MUI component, every component will look default that way. If you need for example need some more custom styling or props, consider creating a reusable component in the shared library. Remember this library does not export components.

important note about Material UI v5

Material UI v5 is just around the corner. This will making theming much easier with for example the possibility of adding custom variants.

For example, take a look in the designs of the Buttons. (https://app.zeplin.io/project/5ecf84a3c6ae1047a368f393/screen/5ecf85c60f301e47ca4eee55) There are multiple variants of the Button specified, but the MUI Button only accepts the default variant names supplied by MUI (contained, outlined, text). In future we could add custom new variants as primary, secondary and tertiary.

If you want to have a reusable Button in this case there are two options:

  • Create a Button story in theme lib, add some buttons with variants of props that are matching with design. For example <Button variant='filled' color="primary" />. You probably need some overriding of styles so that can be added in createTheme at the override section. This is not ideal, as the it's still a bit of matching and combining with props, and not all names of design we can add.
  • Create a Button story in shared lib, import the Button of MUI, and add there all the (custom) variants you need. Colors and style can be retrieved by useTheme hook selector, this is a better solution as it won't break anything, and you name all props like provided in design to keep it consistent.

Guidelines and tips for translating design to code

The designs found in Zeplin can roughly be split in two:

  1. Components
    • Base components following MUI names as Container, Buttons, Elevation, Cards, Elevation, Table
    • Grouped components combining Base components as Header, Top Bar
  2. Modules
    • TimeSlider, Sigmet, LayerManager etc

Layer manager example

In this example, we're going to have a look at the Layer manager. enter image description here

Looking at the design, we can see it's consisting mainly out these elements:

  • Wrapper
  • Top bar
  • Table
  • Footer

Wrapper

This is the first component which holds all sub components which compose the Layermanager. Looking it from a MUI perspective, the first element should be a Paper because it's a surface, and has the background name Background Surface app. The box-shadow is a side effect of elevation, the higher the elevation, the more shadow. Elevation is a default property of Paper, and since the elevations are also defined in the theme we can use those.

Top bar

If you look at the header and forget the left group, you can see it's a header that is used on multiple places like Sigmet dialog header, LayerManager and MultiDimensionSelect but in different sizes. It makes sense to create a reusable component in the shared library with a property size to ensure all sizes work correctly.

link to design: https://app.zeplin.io/project/5ecf84a3c6ae1047a368f393/screen/60f9319044360a123ca42552

Table

The elements of the layermanager are build on top of the table design. In this case, it would make sense to create a new Table* story in the theme library. There we make a story where we are using the MUI Table components, and make sure it has all the correct colors and styling. When that is working correctly, we can use the Table component everywhere and it will look the same everywhere.

Every row renders columns with different inputs; for example for the layers list we can use the MUI MenuItem component. It would make sense to create a separate story for MenuItem, and make sure all base colors are correct.

By wrapping them all together, there's probably some additional styling needed specific for the LayerManager but that's perfectly fine (it can be done in for example the Wrapper described above). The goal is that the MenuItem and Table will have a good default look to use in other places as well.

link to design: https://app.zeplin.io/project/5ecf84a3c6ae1047a368f393/screen/6093e69005029c358090bd4e

*note it's a assumption the Table component will work for the LayerManager. Currently there's a small story demo that shows's the styling so far, but not with for example max-height functionality. This is possible with adding position:sticky to the th elements, but needs further investigation if this component can fully suit our needs.

Footer

This footer is only used for when the wrapper is resizable. Therefore it would make sense to make it part of a Resizable component. It can use the default values from the theme by using useTheme hook.

Do's and dont's

Do's

  • do follow design names, if you feel the name has too much repeat in it or can be changed, ask Didier! Same for missing colors .
  • do add links to design in code
  • do create shared components that are used in opengeoweb application in the shared lib
  • do add a ThemeProvider for components with a new theme styling. Otherwise unit tests will break
  • do discuss the library and the usage of it. If you think it can be approved please let us know!

Don'ts

  • don't add double colors (see TimeSlider example). In dark theme designs, color names suffixed with a (D) are color names defined in the main Color page.
  • don't override the theme for MUI components when they are specific for a design. For example the buttons in the Timeslider don't follow the exact rules of the main Buttons, so it's a good idea to make a custom TimeSliderButton component which uses the colors defined in the theme palette.

Known issues

  • Expect unit tests to fail when using components with the theme. This is easily fixed by wrapping your test with the ThemeProvider from the theme lib. Don't import the ThemeProvider wrapper in the failed test, but use the CoreThemeProvider or CoreThemeStoreProvider found in libs/core/src/lib/components/Providers/Providers.tsx
  • MUI 5 makes it easy to add custom variants to components, so we should wait for that release before investing more heavy in theme stories: https://next.material-ui.com/customization/theme-components/#adding-new-component-variants . Check the progress of the release of v5 here
  • The ThemeProvider imports the CssBaseline components which allows us to set a body background and also resets some initial browser styling values. One thing it's resetting is the css box-sizing property. If you experience misaligning in (canvas) components, this might be the property you want to check out.

Unit testing

nx test theme

Running snapshot tests and updating snapshots locally

Read more about snapshot testing

  1. You need to have docker installed and running.
  2. Start Chromium by running: npm run start-snapshot-server. (This will start a docker container with chromium, to run snapshot tests in. We need this to make sure everyone gets the same snapshot results.)
  3. Run the snapshot tests: npm run test:image-snap-theme. This will first create a new static storybook build and then run the tests.
  4. If a snapshot test fails, you can find and inspect the differences in libs/theme/src/lib/__image_snapshots__/__diff_output__/.
  5. To update the snapshots, run npm run test:image-snap-theme-update. Snapshots are saved under libs/theme/src/lib/__image_snapshots__/. Make sure to commit the new snapshots.
  6. Stop Chromium by running: npm run stop-snapshot-server.

Questions and feedback

Everything written here and coded is open for feedback. If you have any code related questions, please contact the GeoWeb team, if you have any questions about the design, naming and or guidelines please contact the designer Didier

Documentation

https://opengeoweb.gitlab.io/opengeoweb/docs/theme/

Written with StackEdit.

9.16.0

7 days ago

9.15.0

21 days ago

9.14.0

1 month ago

9.13.0

1 month ago

9.12.0

2 months ago

9.11.0

2 months ago

9.10.2

2 months ago

9.10.0

2 months ago

9.10.1

2 months ago

9.9.0

3 months ago

9.8.0

3 months ago

9.7.0

3 months ago

9.6.0

4 months ago

9.5.0

4 months ago

9.4.1

4 months ago

9.4.0

4 months ago

9.3.1

5 months ago

9.3.0

5 months ago

9.2.0

5 months ago

5.1.1

10 months ago

5.1.0

10 months ago

6.1.0

8 months ago

6.1.1

8 months ago

8.4.1

6 months ago

8.4.0

6 months ago

8.1.0

7 months ago

8.3.0

7 months ago

9.1.0

5 months ago

5.2.1

9 months ago

5.2.0

9 months ago

5.0.1

10 months ago

5.0.0

10 months ago

6.0.1

9 months ago

6.0.0

9 months ago

6.0.3

9 months ago

6.0.2

9 months ago

6.0.4

9 months ago

8.3.1

7 months ago

7.0.0

8 months ago

4.22.1

10 months ago

8.0.0

8 months ago

8.2.0

7 months ago

9.0.0

6 months ago

4.22.0

11 months ago

4.21.0

11 months ago

4.19.0

1 year ago

4.19.1

1 year ago

4.20.0

11 months ago

4.14.1

1 year ago

4.16.0

1 year ago

4.18.0

1 year ago

4.15.0

1 year ago

4.15.1

1 year ago

4.17.0

1 year ago

4.11.0

1 year ago

4.12.0

1 year ago

4.13.0

1 year ago

4.13.1

1 year ago

4.8.0

1 year ago

4.7.1

1 year ago

4.9.1

1 year ago

4.7.0

1 year ago

4.6.1

1 year ago

4.10.0

1 year ago

4.5.0

1 year ago

4.6.0

1 year ago

4.4.0

2 years ago

4.3.0

2 years ago

4.2.0

2 years ago

3.0.0

2 years ago

2.15.0

2 years ago

4.1.0

2 years ago

4.0.0

2 years ago

2.13.0

2 years ago

2.14.0

2 years ago

2.12.0

2 years ago

2.10.0

2 years ago

2.7.0

2 years ago

2.9.0

2 years ago

2.8.0

2 years ago

2.5.0

2 years ago

2.6.0

2 years ago

2.3.0

2 years ago

2.4.1

2 years ago

2.4.0

2 years ago

2.2.1

2 years ago

2.1.2

2 years ago

2.2.0

2 years ago

2.1.4

2 years ago

2.1.3

2 years ago

2.1.1

2 years ago

2.1.0

2 years ago

2.0.1

3 years ago

2.0.0

4 years ago

1.0.2

4 years ago