Skip to content

Commit

Permalink
Merge pull request #39 from sillsdev/mui_theming_colors
Browse files Browse the repository at this point in the history
MUI theming colors (#39)
  • Loading branch information
andrew-polk authored Dec 9, 2024
2 parents 993ae91 + 77c0934 commit bb0add7
Show file tree
Hide file tree
Showing 16 changed files with 549 additions and 402 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ Install with npm:
npm i @ethnolib/language-chooser-react-mui
```

### Sizing
### Sizing and Colors

The LanguageChooser will grow to fit the size of its parent. Height and width should be set on the parent. An explicit size should be set on its parent. Our recommended size is 1084x586 (to fit on a low-end notebook with windows task bar) but it will work in a variety of sizes.

The Language Chooser will adopt the primary color of the [MUI theme](https://mui.com/material-ui/customization/theming/) and by default derive the card colors from the primary color. This can be overriden with the `languageCardBackgroundColorOverride` and `scriptCardBackgroundColorOverride` props, or by `setting theme.palette.primary.lighter` (used for the language card color) and `theme.palette.primary.lightest` (used for the script card color) in your MUI theme.

### Props

- `searchResultModifier: (
Expand All @@ -42,6 +44,8 @@ The LanguageChooser will grow to fit the size of its parent. Height and width sh
- `onSelectionChange?: (orthographyInfo: IOrthography | undefined, languageTag: string | undefined) => void` - Whenever the user makes or unselects a complete language selection, this function will be called with the selected language information or undefined, respectively.
- `rightPanelComponent?: React.ReactNode` - A slot for a component to go in the space on the upper right side of the language chooser. See the Storybook Dialog Demo -> Additional Right Panel Component for an example.
- `actionButtons?: React.ReactNode` - A slot for dialog action buttons, e.g. Ok and Cancel. See the [LanguageChooserDialog.tsx](./src/demos/LanguageChooserDialog.tsx) example.
- `languageCardBackgroundColorOverride?: string` - The language chooser will adopt the primary color of the MUI theme. By default, it will make the language card backgrounds be the primary color but 70% lighter (or use theme.palette.primary.lighter if it is set). If provided, this prop will override this background color behavior. See the Storybook Dialog Demo -> withCardBackgroundColorOverrides for an example.
- `scriptCardBackgroundColorOverride?: string` - The language chooser will adopt the primary color of the MUI theme. By default, it will make the script card backgrounds be the primary color but 88% lighter (or use theme.palette.primary.lightest if it is set). If provided, this prop will override this background color behavior. See the Storybook Dialog Demo -> withCardBackgroundColorOverrides for an example.

### Demos

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { Button, ButtonProps, Tooltip, Typography } from "@mui/material";
import {
Button,
ButtonProps,
Tooltip,
Typography,
useTheme,
} from "@mui/material";
import EditIcon from "@mui/icons-material/Edit";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import { COLORS } from "./colors";

export const CustomizeLanguageButton: React.FunctionComponent<
{
Expand All @@ -15,16 +20,17 @@ export const CustomizeLanguageButton: React.FunctionComponent<
forUnlistedLanguage: showAsUnlistedLanguage,
...buttonProps
}) => {
const theme = useTheme();
return (
<Button
variant="outlined"
color="primary"
css={css`
border: 1.5px solid ${COLORS.greys[1]};
border: 1.5px solid ${theme.palette.grey[300]};
:hover {
border-color: ${COLORS.black};
border-color: ${theme.palette.text.primary};
}
background-color: ${COLORS.white};
background-color: ${theme.palette.background.paper};
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
display: flex;
flex-direction: column;
Expand All @@ -37,7 +43,7 @@ export const CustomizeLanguageButton: React.FunctionComponent<
<Typography
css={css`
text-transform: uppercase;
color: ${COLORS.black};
color: ${theme.palette.text.primary};
font-size: 0.75rem;
font-weight: bold;
display: flex; // for the icon
Expand Down Expand Up @@ -70,7 +76,7 @@ export const CustomizeLanguageButton: React.FunctionComponent<
variant="body2"
css={css`
text-align: left;
color: ${COLORS.greys[3]};
color: ${theme.palette.grey[700]};
`}
>
{currentTagPreview}
Expand All @@ -79,12 +85,12 @@ export const CustomizeLanguageButton: React.FunctionComponent<
title={
showAsUnlistedLanguage
? "If you cannot find a language and it does not appear in ethnologue.com, you can instead define the language here."
: "If you found main the language but need to change some of the specifics like Script or Dialect, you can do that here."
: "If you found the main language but need to change some of the specifics like Script or Dialect, you can do that here."
}
>
<InfoOutlinedIcon
css={css`
color: ${COLORS.greys[3]};
color: ${theme.palette.grey[700]};
margin-left: 10px;
`}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import {
DialogTitle,
Typography,
Card,
useTheme,
} from "@mui/material";
import InfoIcon from "@mui/icons-material/Info";
import { TextInput } from "./TextInput";
import { COLORS } from "./colors";
import {
getAllRegions,
getAllScripts,
Expand Down Expand Up @@ -114,6 +114,8 @@ export const CustomizeLanguageDialog: React.FunctionComponent<{
);
}, [props, EMPTY_COMBOBOX_VALUE]);

const theme = useTheme();

return (
<Dialog
onClose={props.onClose}
Expand Down Expand Up @@ -149,14 +151,14 @@ export const CustomizeLanguageDialog: React.FunctionComponent<{
<Card
variant="outlined"
css={css`
border: 1px solid ${COLORS.blues[2]};
border: 1px solid ${theme.palette.primary.main};
padding: 7px;
flex-shrink: 0;
`}
>
<Typography
css={css`
color: ${COLORS.blues[2]};
color: ${theme.palette.primary.main};
font-size: 0.875rem;
display: flex;
align-items: start;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { InputLabel, InputLabelProps, Typography } from "@mui/material";
import { COLORS } from "./colors";
import {
InputLabel,
InputLabelProps,
Typography,
useTheme,
} from "@mui/material";

export const FormFieldLabel: React.FunctionComponent<
{
label: string;
required?: boolean;
} & InputLabelProps
> = ({ label, required, ...inputLabelProps }) => {
const theme = useTheme();
return (
<InputLabel
{...inputLabelProps}
Expand All @@ -19,7 +24,7 @@ export const FormFieldLabel: React.FunctionComponent<
>
<Typography
css={css`
color: ${COLORS.greys[3]};
color: ${theme.palette.grey[700]};
font-weight: bold;
margin-bottom: 3px;
`}
Expand All @@ -29,7 +34,7 @@ export const FormFieldLabel: React.FunctionComponent<
<sup
css={css`
font-weight: normal;
color: ${COLORS.error};
color: ${theme.palette.error.main};
`}
>
(required)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ import {
} from "./OptionCard";
import { ILanguage } from "@ethnolib/find-language";
import { PartiallyBoldedTypography } from "./PartiallyBoldedTypography";
import { COLORS } from "./colors";
import { useTheme } from "@mui/material";

const COMMA_SEPARATOR = ", ";

export const LanguageCard: React.FunctionComponent<
{ languageCardData: ILanguage } & OptionCardPropsWithoutColors
> = ({ languageCardData, ...partialOptionCardProps }) => {
const theme = useTheme();
const optionCardProps = {
...partialOptionCardProps,
backgroundColorWhenNotSelected: COLORS.white,
backgroundColorWhenSelected: COLORS.blues[0],
backgroundColorWhenNotSelected: theme.palette.background.paper,
backgroundColorWhenSelected: theme.palette.primary.lighter,
} as OptionCardProps;
return (
<OptionCard {...optionCardProps}>
Expand Down Expand Up @@ -55,7 +56,7 @@ export const LanguageCard: React.FunctionComponent<
css={css`
flex-grow: 0;
margin-bottom: 1px; // for visual alignment
color: ${COLORS.greys[3]};
color: ${theme.palette.grey[700]};
`}
>
{languageCardData.languageSubtag}
Expand All @@ -72,7 +73,7 @@ export const LanguageCard: React.FunctionComponent<
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
color: ${COLORS.greys[3]};
color: ${theme.palette.grey[700]};
`}
>{`A language of ${languageCardData.regionNames}`}</PartiallyBoldedTypography>
)}
Expand All @@ -82,7 +83,7 @@ export const LanguageCard: React.FunctionComponent<
variant="subtitle1"
css={css`
text-wrap: balance;
color: ${COLORS.greys[3]};
color: ${theme.palette.grey[700]};
`}
>
{languageCardData.names.join(COMMA_SEPARATOR)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
Icon,
IconButton,
InputAdornment,
lighten,
List,
ListItem,
OutlinedInput,
Typography,
useTheme,
} from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import ClearIcon from "@mui/icons-material/Clear";
Expand All @@ -24,7 +26,6 @@ import {
} from "@ethnolib/find-language";
import { LanguageCard } from "./LanguageCard";
import { ScriptCard } from "./ScriptCard";
import { COLORS } from "./colors";
import {
useLanguageChooser,
isUnlistedLanguage,
Expand All @@ -40,43 +41,52 @@ import { CustomizeLanguageDialog } from "./CustomizeLanguageDialog";
import LazyLoad from "react-lazyload";
import { FuseResult } from "fuse.js";
import { FormFieldLabel } from "./FormFieldLabel";
import { TypographyOptions } from "@mui/material/styles/createTypography";

// TODO future work: move all the colors used into the theme
export const languageChooserMuiTheme = createTheme({
palette: {
primary: {
main: COLORS.blues[2],
},
// so we can put "lighter" in the mui theme palette
// https://mui.com/material-ui/customization/palette/#typescript-2
declare module "@mui/material/styles" {
interface PaletteColor {
lighter?: string;
lightest?: string;
}

interface SimplePaletteColorOptions {
lighter?: string;
lightest?: string;
}
}

const languageChooserTypography: TypographyOptions = {
// TODO future work: figure out how to incorporate base theme typography?
h1: {
// Used by the top "Language Chooser" title bar
fontSize: "1.25rem",
fontWeight: 600,
lineHeight: 1.6,
letterSpacing: "0.0075em",
},
typography: {
h1: {
// Used by the top "Language Chooser" title bar
fontSize: "1.25rem",
fontWeight: 600,
lineHeight: 1.6,
letterSpacing: "0.0075em",
},
h2: {
// Used for the primary language and script name(s)
fontSize: "1rem",
fontWeight: 400,
lineHeight: 1.5,
letterSpacing: "0.00938em",
},
subtitle1: {
// Used for list of language regions and other language names
fontSize: "0.75rem",
lineHeight: 1.167,
letterSpacing: "0.001em", // I'm not sure how MUI calculates its default letter spacings, but this looks about right
},
body2: {
// used for language codes and tags
fontFamily: "Roboto Mono, monospace",
fontSize: "0.875rem",
letterSpacing: "0.05rem",
},
h2: {
// Used for the primary language and script name(s)
fontSize: "1rem",
fontWeight: 400,
lineHeight: 1.5,
letterSpacing: "0.00938em",
},
});
subtitle1: {
// Used for list of language regions and other language names
fontSize: "0.75rem",
lineHeight: 1.167,
letterSpacing: "0.001em", // I'm not sure how MUI calculates its default letter spacings, but this looks about right
},
body2: {
// used for language codes and tags
fontFamily: "Roboto Mono, monospace",
fontSize: "0.875rem",
letterSpacing: "0.05rem",
},
};

const LANG_CARD_MIN_HEIGHT = "90px"; // The height of typical card - 1 line of alternate names and 1 line of regions

export interface ILanguageChooserProps {
Expand All @@ -93,6 +103,8 @@ export interface ILanguageChooserProps {
) => void;
rightPanelComponent?: React.ReactNode;
actionButtons?: React.ReactNode;
languageCardBackgroundColorOverride?: string; // if not provided, will use theme.palette.primary.lighter if present or fall back to lighten(primaryColor, 0.7)
scriptCardBackgroundColorOverride?: string; // if not provided, will use theme.palette.primary.lightest if present or fall back to lighten(primaryColor, 0.88)
}

export const LanguageChooser: React.FunctionComponent<ILanguageChooserProps> = (
Expand Down Expand Up @@ -194,8 +206,30 @@ export const LanguageChooser: React.FunctionComponent<ILanguageChooserProps> = (
lp.onSearchStringChange("");
};

const originalTheme = useTheme();
const primaryMainColor = originalTheme.palette.primary.main;
const theme = createTheme({
...originalTheme,
typography: languageChooserTypography,
palette: {
...originalTheme.palette,
primary: {
...originalTheme.palette.primary,
// mui palettes have a "light" also, but for the card backgrounds we want very light colors, lighter than "light" usually is
lighter:
props.languageCardBackgroundColorOverride ||
originalTheme.palette.primary.lighter ||
lighten(primaryMainColor, 0.7),
lightest:
props.scriptCardBackgroundColorOverride ||
originalTheme.palette.primary.lightest ||
lighten(primaryMainColor, 0.88),
},
},
});

return (
<ThemeProvider theme={languageChooserMuiTheme}>
<ThemeProvider theme={theme}>
<div
id="lang-chooser-body"
css={css`
Expand All @@ -214,7 +248,7 @@ export const LanguageChooser: React.FunctionComponent<ILanguageChooserProps> = (
display: flex; // to make the language list overflow scroll work
flex-direction: column;
padding: 10px 10px 10px 15px;
background-color: ${COLORS.greys[0]};
background-color: ${theme.palette.grey[50]};
`}
>
<FormFieldLabel
Expand All @@ -241,7 +275,7 @@ export const LanguageChooser: React.FunctionComponent<ILanguageChooserProps> = (
position="start"
css={css`
margin-left: 0;
color: ${COLORS.greys[2]};
color: ${theme.palette.grey[400]};
`}
>
<Icon component={SearchIcon} />
Expand Down Expand Up @@ -416,7 +450,7 @@ export const LanguageChooser: React.FunctionComponent<ILanguageChooserProps> = (
<Typography
variant="body2"
css={css`
color: ${COLORS.greys[3]};
color: ${theme.palette.grey[700]};
`}
>
{currentTagPreview}
Expand Down
Loading

0 comments on commit bb0add7

Please sign in to comment.