Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Link] Add support for CSS variables #33036

Merged
merged 10 commits into from
Jun 9, 2022
113 changes: 113 additions & 0 deletions docs/pages/experiments/material-ui/link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import * as React from 'react';
import {
Experimental_CssVarsProvider as CssVarsProvider,
useColorScheme,
} from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Container from '@mui/material/Container';
import Link from '@mui/material/Link';
import Moon from '@mui/icons-material/DarkMode';
import Sun from '@mui/icons-material/LightMode';

const ColorSchemePicker = () => {
const { mode, setMode } = useColorScheme();
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}

return (
<Button
variant="outlined"
onClick={() => {
if (mode === 'light') {
setMode('dark');
} else {
setMode('light');
}
}}
>
{mode === 'light' ? <Moon /> : <Sun />}
</Button>
);
};

export default function CssVarsTemplate() {
return (
<CssVarsProvider>
<CssBaseline />
<Container sx={{ my: 5 }}>
<Box sx={{ pb: 2 }}>
<ColorSchemePicker />
</Box>
<Box
sx={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(256px, 1fr))',
gridAutoRows: 'minmax(160px, auto)',
gap: 2,
'& > div': {
placeSelf: 'center',
},
}}
>
<Box>
<Link href="./#" underline="always">
Link
</Link>
</Box>
<Box>
<Link href="./#" color="inherit" underline="always">
{'color="inherit"'}
</Link>
</Box>
<Box>
<Link href="./#" color="secondary" underline="always">
{'color="secondary"'}
</Link>
</Box>
<Box>
<Link href="./#" color="error" underline="always">
{'color="error"'}
</Link>
</Box>
<Box>
<Link href="./#" color="textPrimary" underline="always">
{'color="textPrimary"'}
</Link>
</Box>
<Box>
<Link href="./#" color="textSecondary" underline="always">
{'color="textSecondary"'}
</Link>
</Box>
<Box>
<Link href="./#" color="#123abc" underline="always">
{'color="#123abc"'}
</Link>
</Box>
<Box>
<Link href="./#" color="#123" underline="always">
{'color="#123"'}
</Link>
</Box>
<Box>
<Link href="./#" color="rgb(255, 0, 255)" underline="always">
{'color="rgb(255, 0, 255)"'}
</Link>
</Box>
<Box>
<Link href="./#" color="rgba(255, 0, 255, 0.8)" underline="always">
{'color="rgba(255, 0, 255, 0.8)"'}
</Link>
</Box>
</Box>
</Container>
</CssVarsProvider>
);
}
20 changes: 4 additions & 16 deletions packages/mui-material/src/Link/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,14 @@ import PropTypes from 'prop-types';
import clsx from 'clsx';
import { elementTypeAcceptingRef } from '@mui/utils';
import { unstable_composeClasses as composeClasses } from '@mui/base';
import { alpha, getPath } from '@mui/system';
import capitalize from '../utils/capitalize';
import styled from '../styles/styled';
import useThemeProps from '../styles/useThemeProps';
import useIsFocusVisible from '../utils/useIsFocusVisible';
import useForkRef from '../utils/useForkRef';
import Typography from '../Typography';
import linkClasses, { getLinkUtilityClass } from './linkClasses';

const colorTransformations = {
primary: 'primary.main',
textPrimary: 'text.primary',
secondary: 'secondary.main',
textSecondary: 'text.secondary',
error: 'error.main',
};

const transformDeprecatedColors = (color) => {
return colorTransformations[color] || color;
};
import getTextDecoration, { colorTransformations } from './getTextDecoration';

const useUtilityClasses = (ownerState) => {
const { classes, component, focusVisible, underline } = ownerState;
Expand Down Expand Up @@ -52,8 +40,6 @@ const LinkRoot = styled(Typography, {
];
},
})(({ theme, ownerState }) => {
const color =
getPath(theme, `palette.${transformDeprecatedColors(ownerState.color)}`) || ownerState.color;
return {
...(ownerState.underline === 'none' && {
textDecoration: 'none',
Expand All @@ -66,7 +52,9 @@ const LinkRoot = styled(Typography, {
}),
...(ownerState.underline === 'always' && {
textDecoration: 'underline',
textDecorationColor: color !== 'inherit' ? alpha(color, 0.4) : undefined,
...(ownerState.color !== 'inherit' && {
textDecorationColor: getTextDecoration({ theme, ownerState }),
}),
'&:hover': {
textDecorationColor: 'inherit',
},
Expand Down
117 changes: 117 additions & 0 deletions packages/mui-material/src/Link/getTextDecoration.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { expect } from 'chai';
import { createTheme, experimental_extendTheme as extendTheme } from '../styles';
import getTextDecoration from './getTextDecoration';

describe('getTextDecoration', () => {
describe('without theme.vars', () => {
const theme = createTheme();
it('deprecated color', () => {
expect(getTextDecoration({ theme, ownerState: { color: 'primary' } })).to.equal(
'rgba(25, 118, 210, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'textPrimary' } })).to.equal(
'rgba(0, 0, 0, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'secondary' } })).to.equal(
'rgba(156, 39, 176, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'textSecondary' } })).to.equal(
'rgba(0, 0, 0, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'error' } })).to.equal(
'rgba(211, 47, 47, 0.4)',
);
});

it('system color', () => {
expect(getTextDecoration({ theme, ownerState: { color: 'primary.main' } })).to.equal(
'rgba(25, 118, 210, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'text.primary' } })).to.equal(
'rgba(0, 0, 0, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'secondary.main' } })).to.equal(
'rgba(156, 39, 176, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'text.secondary' } })).to.equal(
'rgba(0, 0, 0, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'error.main' } })).to.equal(
'rgba(211, 47, 47, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'grey.500' } })).to.equal(
'rgba(158, 158, 158, 0.4)',
);
});

it('valid CSS color', () => {
expect(getTextDecoration({ theme, ownerState: { color: '#000' } })).to.equal(
'rgba(0, 0, 0, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'rgb(1, 1, 1)' } })).to.equal(
'rgba(1, 1, 1, 0.4)',
);
expect(() => getTextDecoration({ theme, ownerState: { color: 'yellow' } })).to.throw();
});
});

describe('CSS variables', () => {
const theme = extendTheme();
theme.palette = theme.colorSchemes.light.palette;
theme.vars = theme.colorSchemes.light;
theme.vars.palette.primary.mainChannel = 'var(--palette-primary-main)';
theme.vars.palette.secondary.mainChannel = 'var(--palette-secondary-main)';
theme.vars.palette.text.primaryChannel = 'var(--palette-text-primary)';
theme.vars.palette.text.secondaryChannel = 'var(--palette-text-secondary)';
theme.vars.palette.error.mainChannel = 'var(--palette-error-main)';
// in the application, the value will be CSS variable: `rgba(var(--the-color-channel) / 0.4)`
it('deprecated color', () => {
expect(getTextDecoration({ theme, ownerState: { color: 'primary' } })).to.equal(
'rgba(var(--palette-primary-main) / 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'textPrimary' } })).to.equal(
'rgba(var(--palette-text-primary) / 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'secondary' } })).to.equal(
'rgba(var(--palette-secondary-main) / 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'textSecondary' } })).to.equal(
'rgba(var(--palette-text-secondary) / 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'error' } })).to.equal(
'rgba(var(--palette-error-main) / 0.4)',
);
});

it('system color', () => {
expect(getTextDecoration({ theme, ownerState: { color: 'primary.main' } })).to.equal(
'rgba(var(--palette-primary-main) / 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'text.primary' } })).to.equal(
'rgba(var(--palette-text-primary) / 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'secondary.main' } })).to.equal(
'rgba(var(--palette-secondary-main) / 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'text.secondary' } })).to.equal(
'rgba(var(--palette-text-secondary) / 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'error.main' } })).to.equal(
'rgba(var(--palette-error-main) / 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'grey.500' } })).to.equal(
'rgba(158, 158, 158, 0.4)',
);
});

it('valid CSS color', () => {
expect(getTextDecoration({ theme, ownerState: { color: '#000' } })).to.equal(
'rgba(0, 0, 0, 0.4)',
);
expect(getTextDecoration({ theme, ownerState: { color: 'rgb(1, 1, 1)' } })).to.equal(
'rgba(1, 1, 1, 0.4)',
);
expect(() => getTextDecoration({ theme, ownerState: { color: 'yellow' } })).to.throw();
});
});
});
33 changes: 33 additions & 0 deletions packages/mui-material/src/Link/getTextDecoration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { alpha, getPath } from '@mui/system';
import type { Theme } from '../styles';

export const colorTransformations = {
primary: 'primary.main',
textPrimary: 'text.primary',
secondary: 'secondary.main',
textSecondary: 'text.secondary',
error: 'error.main',
};

const transformDeprecatedColors = (color: string) => {
return colorTransformations[color as keyof typeof colorTransformations] || color;
};

const getTextDecoration = <T extends Theme>({
theme,
ownerState,
}: {
theme: T;
ownerState: { color: string };
}) => {
const transformedColor = transformDeprecatedColors(ownerState.color);
const color = (getPath(theme, `palette.${transformedColor}`, false) ||
ownerState.color) as string;
const channelColor = getPath(theme, `palette.${transformedColor}Channel`) as string | null;
if ('vars' in theme && channelColor) {
return `rgba(${channelColor} / 0.4)`;
}
return alpha(color, 0.4);
};

export default getTextDecoration;
14 changes: 9 additions & 5 deletions packages/mui-material/src/styles/createPalette.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import blue from '../colors/blue';
import lightBlue from '../colors/lightBlue';
import green from '../colors/green';

export const light = {
const getLightTokens = () => ({
// The colors used to style the text.
text: {
// The most important text.
Expand Down Expand Up @@ -47,9 +47,11 @@ export const light = {
focusOpacity: 0.12,
activatedOpacity: 0.12,
},
};
});

export const dark = {
export const light = getLightTokens();

const getDarkTokens = () => ({
text: {
primary: common.white,
secondary: 'rgba(255, 255, 255, 0.7)',
Expand All @@ -74,7 +76,9 @@ export const dark = {
focusOpacity: 0.12,
activatedOpacity: 0.24,
},
};
});

export const dark = getDarkTokens();

function addLightOrDark(intent, direction, shade, tonalOffset) {
const tonalOffsetLight = tonalOffset.light || tonalOffset;
Expand Down Expand Up @@ -261,7 +265,7 @@ export default function createPalette(palette) {
return color;
};

const modes = { dark, light };
const modes = { dark: getDarkTokens(), light: getLightTokens() };
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

if (process.env.NODE_ENV !== 'production') {
if (!modes[mode]) {
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-material/src/styles/createPalette.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('createPalette()', () => {
expect(palette.secondary.main, 'should use purple as the default secondary color').to.equal(
purple[200],
);
expect(palette.text, 'should use dark theme text').to.equal(dark.text);
expect(palette.text, 'should use dark theme text').to.deep.equal(dark.text);
});

describe('augmentColor', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/mui-system/src/style.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export interface StyleOptions<PropKey> {
export function style<PropKey extends string, Theme extends object>(
options: StyleOptions<PropKey>,
): StyleFunction<{ [K in PropKey]?: unknown } & { theme?: Theme }> & { filterProps: string[] };
export function getPath<T>(obj: T, path: string | undefined, checkVars?: boolean): null | unknown;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I tried to import the function in typescript, it could not find the typings so I added it here.