- Session 1 Walkthrough
git checkout session-1-start
The PageHeader
in Canvas Kit is very similar, but we can build our own too. Let's do that!
touch src/components/PageHeader.tsx
We'll create an initial PageHeader
like this:
import * as React from "react";
import { gradients } from "@workday/canvas-kit-react/tokens";
import { Box } from "./common/primitives";
import { Heading } from "./common/type";
export const PageHeader: React.FC = (props) => {
return (
<Box
as="header"
paddingX="xl"
paddingY="m"
backgroundImage={gradients.blueberry}
{...props}
>
<Heading fontWeight="regular" variant="inverse" {...props}>
Canvas Coffee Roasters
</Heading>
</Box>
);
};
But maybe you want this header to be more flexible and composable. You could also write it like this:
import * as React from "react";
import { gradients } from "@workday/canvas-kit-react/tokens";
import { Box, BoxProps } from "./common/primitives";
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 fontWeight="regular" variant="inverse" {...props}>
{props.children}
</Heading>
);
};
export const App: React.FC = (props) => {
return (
<CanvasProvider>
<FontContainer>
<Global styles={css(globalStyles)} />
<PageHeader>
<PageHeader.Heading>Canvas Coffee Roasters</PageHeader.Heading>
</PageHeader>
{props.children}
</FontContainer>
</CanvasProvider>
);
};
git add .
git status
git commit -m "Add PageHeader component"
Now that we have a PageHeader
, we can build a Sidebar
.
touch src/components/Sidebar.tsx
First we'll use Box
to create our Sidebar
.
import * as React from "react";
import { Box } from "./common/primitives/Box";
export const Sidebar: React.FC = ({ children }) => {
return (
<Box
as="aside"
minWidth={400}
borderRight="solid 1px"
borderRightColor="soap400"
padding="xl"
>
{children}
</Box>
);
};
Then you'll import it to App.tsx
to implement it. We'll wrap it in a Flex
to build our layout.
export const App: React.FC = (props) => {
return (
{/* other JSX omitted */}
<Flex minHeight="100vh">
<Sidebar>Hello, Sidebar!</Sidebar>
{props.children}
</Flex>
{/* other JSX omitted */}
);
}
Et voilà! A simple Sidebar
.
git add .
git status
git commit -m "Add Sidebar component"
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
.
export const Home: React.FC = () => {
const { coffee } = useAllCoffee();
return (
<Flex as="main" flexDirection="column" flex={1} 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!
git add .
git status
git commit -m "Update Home content"
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.
touch src/components/Card.tsx
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, Detail } 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>
<Detail size="large" variant="hint">
$17.40 | 12 oz.
</Detail>
</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 "./common/layout";
import { Box } from "./common/primitives";
import { Body, Detail } 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>
<Detail size="large" variant="hint">
$17.40 | 12 oz.
</Detail>
</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.
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">
<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 "./common/layout";
import { Body, BodyProps, Detail, DetailProps } 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.Detail = ({ children, ...props }: DetailProps) => {
return (
<Detail size="large" variant="hint" {...props}>
{children}
</Detail>
);
};
Awesome! You did it! Now let's wire up the cards to our data!
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.Detail>
${brew.price} | {brew.bagWeight} oz.
</Card.Detail>
</Card.Content>
</Card>
))}
</Flex>
</Flex>
);
};
Awesome! Way to go!
git add .
git status
git commit -m "Add Card component and wire up card layout"
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!