- View the Session 1 Start Template
- Click the fork button on the top right
The PageHeader
in Canvas Kit is very similar, but we can build our own too. Let's do that!
First, we'll add the file
src/components/PageHeader.tsx
We could create an initial PageHeader
like this:
/** @jsx jsx */
import { jsx, css } from "@emotion/react";
// import styled from '@emotion/styled';
import * as React from "react";
import { gradients, space, type } from "@workday/canvas-kit-react/tokens";
const pageHeaderStyles = css({
backgroundImage: gradients.blueberry,
padding: `${space.m} ${space.xl}`,
});
const pageHeadingStyles = css({
...type.levels.heading.medium,
...type.variants.inverse,
fontWeight: type.properties.fontWeights.regular,
margin: 0,
});
export const PageHeader: React.FC = ({ children, ...props }) => (
<header {...props} css={pageHeaderStyles}>
<h1 css={pageHeadingStyles}>Canvas Coffee Roasters</h1>
</header>
);
Or we could use Box
import * as React from "react";
import { gradients } from "@workday/canvas-kit-react/tokens";
import { Box, BoxProps } from "@workday/canvas-kit-labs-react/common";
import { Heading, HeadingProps } from "./common/type";
export const PageHeader = ({ children, ...props }: BoxProps) => {
return (
<Box
as="header"
paddingX="xl"
paddingY="m"
backgroundImage={gradients.blueberry}
{...props}
>
{children}
</Box>
);
};
PageHeader.Heading = (props: HeadingProps) => {
return (
<Heading as="h1" fontWeight="regular" variant="inverse" {...props}>
{props.children}
</Heading>
);
};
Now that we have a PageHeader
, we can build a Sidebar
.
First, we'll add the file:
src/components/Sidebar.tsx
import * as React from "react";
import { Box, BoxProps } from "@workday/canvas-kit-labs-react/common";
export const Sidebar = ({ children, ...props }: BoxProps) => {
return (
<Box
as="aside"
minWidth={400}
borderRight="solid 1px"
borderRightColor="soap400"
padding="xl"
{...props}
>
{children}
</Box>
);
};
Then you'll import it to App.tsx
to implement it. We'll wrap it in a Flex
to build our layout.
Et voilà! A simple Sidebar
.
Now we'll update the main content in Home/index.tsx
. We'll update the element to main
with the as
prop, add 40px
of padding and set flex
to 1
.
Note: IE11 does not properly support
flexBasis: 0
. If you're using theflexBasis
prop orflex
(which implicitly setsflex-basis
to0
), you'll likely want to manually setflexBasis
toauto
. E.g.flex="1 1 auto"
orflex={1} flexBasis="auto"
export const Home: React.FC = () => {
const { coffee } = useAllCoffee();
return (
<Flex as="main" flexDirection="column" flex="1 1 auto" padding="xl">
<Title>Hello, Canvas Kit Workshop!</Title>
<Body>Coffee Count: {coffee.length}</Body>
</Flex>
);
};
Boom! Done! That's our page layout! Next up: Card Layout!
Similar to PageHeader
, we could use the Card
component in Canvas Kit, and modify it. Or we could create our own. Let's see what we can do.
First, we'll add the file:
src/components/Card.tsx
Now let's start by building a static card following the spec in Figma.
import * as React from "react";
import { Flex } from "./common/layout";
import { Box } from "./common/primitives";
import { Body, Subtext } from "./common/type";
import { Image } from "./Image";
export const Card: React.FC = () => {
return (
<Flex
margin="s"
flexDirection="column"
flexBasis="240px"
alignSelf="stretch"
flexGrow={0}
padding="l"
depth={2}
backgroundColor="frenchVanilla100"
>
<Image
height="100%"
padding="s"
type="blackberry"
alt={`blackberry coffee bag`}
/>
<Flex marginBottom="xxs">
<Body as="h3" fontWeight="bold" size="large">
El Salvador Apaneca
</Body>
</Flex>
<Box marginBottom="xxs">
<Body>Toffee & Cocoa</Body>
</Box>
<Subtext size="large" variant="hint">
$17.40 | 12 oz.
</Subtext>
</Flex>
);
};
Great! This looks good, but it would be nice to clean up those Flex
wrappers around the text. This is a situation where Stack
comes in handy.
import * as React from "react";
import { Flex, Stack } from "@workday/canvas-kit-labs-react/layout";
import { Heading, Body, Subtext } from "./common/type";
import { Image } from "./Image";
export const Card: React.FC = () => {
return (
<Flex
margin="s"
flexDirection="column"
flexBasis="240px"
alignSelf="stretch"
flexGrow={0}
padding="l"
depth={2}
backgroundColor="frenchVanilla100"
>
<Image
height="100%"
padding="s"
type="blackberry"
alt={`blackberry coffee bag`}
/>
<Stack spacing="xxs" flexDirection="column">
<Body as="h3" fontWeight="bold" size="large">
El Salvador Apaneca
</Body>
<Body>Toffee & Cocoa</Body>
<Subtext size="large" variant="hint">
$17.40 | 12 oz.
</Subtext>
</Stack>
</Flex>
);
};
Nice! Stack
is providing consistent spacing between those text elements.
Now that we have our initial Card
built, we can render it in the layout. We'll add a Flex
wrapper to build the layout.
// src/pages/Home/index.tsx
import { Card } from "../../components/Card";
export const Home: React.FC = () => {
const { coffee } = useAllCoffee();
return (
<Flex as="main" flexDirection="column" flex="1 1 auto" padding="xl">
<Flex flexWrap="wrap" alignItems="flex-start">
<Card />
</Flex>
</Flex>
);
};
Cool! It's looking good. Now we need to make Card
more flexible to support dynamic data.
We'll refactor Card
to be more to be more composable.
import * as React from "react";
import {
Flex,
FlexProps,
Stack,
StackProps,
SpacingValue,
} from "@workday/canvas-kit-labs-react/layout";
import { Body, BodyProps, Subtext, SubtextProps } from "./common/type";
import { Image, ImageProps } from "./Image";
export const Card = ({ children, ...props }: FlexProps) => {
return (
<Flex
margin="s"
flexDirection="column"
flexBasis="240px"
alignSelf="stretch"
flexGrow={0}
padding="l"
depth={2}
backgroundColor="frenchVanilla100"
{...props}
>
{children}
</Flex>
);
};
Card.Image = ({ type, alt }: ImageProps) => {
return <Image height="100%" padding="s" type={type} alt={alt} />;
};
type CardContent = Omit<StackProps, "spacing"> & {
spacing?: SpacingValue;
};
Card.Content = ({ children, spacing = "xxs", ...props }: CardContent) => {
return (
<Stack spacing={spacing} flexDirection="column" {...props}>
{children}
</Stack>
);
};
Card.Heading = ({ children, ...props }: BodyProps) => {
return (
<Body as="h3" fontWeight="bold" size="large" {...props}>
{children}
</Body>
);
};
Card.Body = ({ children, ...props }: BodyProps) => {
return <Body {...props}>{children}</Body>;
};
Card.Subtext = ({ children, ...props }: SubtextProps) => {
return (
<Subtext size="large" variant="hint" {...props}>
{children}
</Subtext>
);
};
And here's what the new composable Card API will look like:
// src/pages/Home/index.tsx
// ...
<Card>
<Card.Image type="blackberry" alt="blackberry coffee bag" />
<Card.Content>
<Card.Heading>El Salvador Apaneca</Card.Heading>
<Card.Body>Toffee & Cocoa</Card.Body>
<Card.Subtext>$17.40 | 12 oz.</Card.Subtext>
</Card.Content>
</Card>
Awesome! You did it! Now let's wire up the cards to our data!
// src/pages/Home/index.tsx
import { Card } from "../../components/Card";
export const Home: React.FC = () => {
const { coffee } = useAllCoffee();
return (
<Flex as="main" flexDirection="column" flex={1} padding="xl">
<Flex flexWrap="wrap" alignItems="flex-start">
{coffee.map((brew) => (
<Card key={brew.id}>
<Card.Image type={brew.img} alt={`${brew.name} coffee bag`} />
<Card.Content>
<Card.Heading>{brew.name}</Card.Heading>
<Card.Body>{brew.flavorProfile}</Card.Body>
<Card.Subtext>
${brew.price} | {brew.bagWeight} oz.
</Card.Subtext>
</Card.Content>
</Card>
))}
</Flex>
</Flex>
);
};
You completed Session 1! We learned how to use style props with layout and type components. We also learned how to refactor components to make them more composable and flexible. Good job! See you in Session 2!