import { Meta, Markdown } from '@storybook/addon-docs/blocks'; import { GEL_GROUP_1_SCREEN_WIDTH_MIN, GEL_GROUP_1_SCREEN_WIDTH_MAX, GEL_GROUP_2_SCREEN_WIDTH_MIN, GEL_GROUP_2_SCREEN_WIDTH_MAX, GEL_GROUP_3_SCREEN_WIDTH_MIN, GEL_GROUP_3_SCREEN_WIDTH_MAX, GEL_GROUP_4_SCREEN_WIDTH_MIN, GEL_GROUP_4_SCREEN_WIDTH_MAX, GEL_GROUP_5_SCREEN_WIDTH_MIN, GEL_GROUP_0_SCREEN_WIDTH_MAX, } from '../../src/app/legacy/psammead/gel-foundations/src/breakpoints'; import * as spacings from '../../src/app/components/ThemeProvider/spacings'; import { MARGIN_BELOW_400PX, MARGIN_ABOVE_400PX, GUTTER_BELOW_600PX, GUTTER_ABOVE_600PX, } from '../../src/app/components/ThemeProvider/spacings';
There are helpers in React which will accept children as a prop (with the appropriate type) without us having to declare it.
interface MyComponentProps {
children: React.Node,
prop1: string,
...
}
const MyComponent = ({
prop1,
children
}: MyComponentProps) => {
return (
<TheComponent prop1={prop1}>
{children}
</>
);
}import { PropsWithChildren } from 'react';
interface MyComponentProps {
prop1: string,
...
}
const MyComponent = ({
prop1,
children
}: PropsWithChildren<MyComponentProps>) => {
return (
<TheComponent prop1={prop1}>
{children}
</>
);
}This keeps all types in a single location, which can be easily imported when required. It is recommended to do this if the type/interface declaration is required to be in a shared (but not global) location, if it is used by other components.
(index.tsx)
interface MyComponentProps {
prop1: string,
...
}
const MyComponent = ({ prop1 }: MyComponentProps) => {
...
}(types.ts)
export interface MyComponentProps {
prop1: string,
...
}(index.ts)
import { MyComponentProps } from './types';
const MyComponent = ({ prop1 }: MyComponentProps) => {
...
}Usually if it makes more sense to keep the type/interface declaration right next to the component if it is not required or used anywhere else.
Follow the guidelines outlined in Emotion Docs about how to convert from styled → css
(index.jsx)
const StyledContainer = styled.{html primitive element}`
color: ${props => props.theme.palette.BLACK}
text-align: center;(index.styles.ts)
import { css, Theme } from '@emotion/react';
const styles = {
link: ({ palette }: Theme) =>
css({
color: palette.BLACK,
textAlign: 'center',
}),
};
export default styles;Ensure that the jsx function is imported from the @emotion/react library, per the https://emotion.sh/docs/css-prop#import-the-jsx-function-from-emotionreact docs.
(index.tsx)
import styles from './index.styles';
...
<a css={styles.link} href={href}>Link Text Here</a>const StyledLink = styled.a`
@media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}) {
...cssForGroup
}
`;import { css, Theme } from '@emotion/react';
const styles = {
link: ({ mq }: Theme) =>
css({
[mq.GROUP_2_MIN_WIDTH]: {
...cssForGroup,
},
}),
};
export default styles;When using the styled library it was possible to use any prop to return different css based on conditional logic.
It was possible to pass in props to conditionally change the css
const Timestamp = styled.time`
..timestampStyledProps
color: ${props => props.isAmp ? props.theme.palette.BLACK : props.theme.palette.GREY_3: }
`See
simorgh/src/app/components/FrostedGlassPromo/withData.tsx
Lines 71 to 76 in 70ef6ad
Here, the value of color is based on the value of the isAmp variable, and this variation is added to the list of css functions already applied to the component.
css={theme => [
styles.timestamp,
{
color: isAmp ? theme.palette.BLACK : theme.palette.GREY_3,
},
]}When using the styled library it was possible to use any prop to return different css within a media query.
e.g.
simorgh/src/app/legacy/components/Footer/List/index.jsx
Lines 49 to 58 in 3dd7ead
In this example, itemCount and trustProjectLink are used to calculate the number of rows in grid-template-rows
styled.ul`
...
@media
(min-width: ${GEL_GROUP_1_SCREEN_WIDTH_MIN})
and
(max-width: ${GEL_GROUP_2_SCREEN_WIDTH_MAX}) {
grid-column-gap: ${GEL_SPACING};
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(
${({ itemCount, trustProjectLink }) =>
getRowCount(itemCount, 2, trustProjectLink)},
auto
);
column-count: 2;
}
`;In this example, we have created a function which can take itemCount and trustProjectLink as a prop, and return the correct gridTemplateRows for the respective breakpoints.
const gridTemplateRows = ({
itemCount,
trustProjectLink,
}: {
itemCount: number;
trustProjectLink?: FooterLink;
}) =>
css({
[GROUP_1_AND_GROUP_2]: {
gridTemplateRows: `repeat(${getRowCount({
itemCount,
columns: 2,
trustProjectLink,
})}, auto)`,
},
...
})This function is then called from the component which requires it:
simorgh/src/app/components/Footer/List/index.tsx
Lines 18 to 30 in 2972055
css={[
...
gridTemplateRows({
itemCount: elements.length,
trustProjectLink,
}),
]}Sometimes Typescript errors are unavoidable / cannot be fixed (usually in tests). One way to make the TS compiler ignore errors is to use @ts-ignore. However, usage of @ts-expect-error is preferred, because it requires a description explaining why the error is to be expected. If the error is then fixed in future, the TS compiler will require removal of the @ts-expect-error comment.
No idea why this syntax is ignored, and will remain this way until someone revisits the code and removes the comment.
// @ts-ignore
broken.typescript.syntax;A reason explaining why the syntax is ignored. Once the fix is merged in a future PR, the TS compiler should throw an error, meaning that this comment must then be removed.
// @ts-expect-error ignoring broken TS syntax until ticket 12345 is merged
broken.typescript.syntax;