Introduction and notes on first part
Welcome back for the second part of the Zelda series.
I hope that you manage to complete the first part without much trouble. If not or if you've missed the first part I created a checkpoint branch with all the steps completed.
Also there were some errors in the solutions provided in part 1 that should all be fixed now.
After the first part, our interface should looks like this:
In this second part we'll focus first on the right column of our layout and then we're going to handle items pagination and ways to navigate between them.
For providing solutions I decided to stop using gist files, they were hard to maintain and even for reading it was not convenient either to jump between files for the same solution.
Instead I created a checkpoint commit at each step of the second part, it should make reading easier hopefully.
Displaying additional items information
Right column layout
We're going to start by creating the base blocks of the right column.
We need an ItemInformation component that should be displayed at the bottom of the column. For now the component should be an empty box, but its width should match the ItemGrid width when we are in column display.
Once the ItemInformation component is set, we'll need to position the Link image below and hide it for breakpoints below XL. We can use an absolute here for the positioning.
Notes
The color for the ItemInformation background is missing in the starter tailwind configuration file. We can add it now:
bgBlackTransparent: "rgba(0, 0, 0, 0.5)",
Solution
Items information
Now we're going to display more Item information. This step is mostly passing down props and implementing the design of the ItemInformation component.
We want to display the name, value, category and description of the item. The category will be displayed as an Icon. In the first part we've displayed only one category among the 3 available in the dataset but we'll handle all 3 cases here. We need to create a CategoryIcon component which displays the appropriate icon based on a type props for example.
But first we have to convert our SVGs (Armor, Shield, Sword) inside src/assets/items into React components. We will use SVGR for that.
On this step we will also add the calamity font since this is the first time we will display text. It is available in the font folder under assets. We have to move it to create-react-app public folder and add font-face declarations in a separate css file.
Notes
We can reuse the Armor icon for helm and greave category.
Ressources
Solution
Adding a component to animate text
Now we are going to animate the item description by adding a typewriter effect animation.
To achieve this effect we are going to use the windups library from @sgwil. The library is really well written, has a nice API and great documentation. We can even trigger effects, like sounds, during the animation. For this demo we are just going to use the base functionality of windups with the useWindupString hook provided for React.
This hook renders a lot of time so we should avoid using it at a high level in our app structure otherwise it would trigger a lot of unneeded render on children components and seriously lower the performance of our interface.
Instead we will create a TypeWriter component that we will use inside ItemInformation.
Notes
Have a look at the interactive guides to get a feeling of what you can do with windups or even the sources if you have some time they are really interesting.
Ressources
Solution
Adding pagination
Handling pagination logic
In the first part we create a getItems function to retrieve one category of items and fill the rest of the grid cells with an empty item object.
Now we have to tweak this function a bit to return all the categories of items by page.
The function should have the following ItemsPage return type
export enum ItemsMainCategoriesType {
WEAPONS = "weapons",
SHIELDS = "shields",
ARMORS = "armors",
}
export type ItemsPage = {
items: ItemType[];
mainCategory: ItemsMainCategoriesType;
page: number;
};
Once that is done we need to create two states using useState react hook inside our App component:
- One that should be initialized with the result of our getItems function
- Another one to store the current page, that we initialize to 0 for now
- Then we need to create our new items variable based on the active page
At the end of this step we won't be able to navigate between pages yet but we'll have the base logic ready.
Solution
Creating a component for page navigation
For the next part we're going to create the component to move between the groups of items, we can call it CategoriesMenu. We'll also need a component for each item inside that menu, we can call it CategoriesMenuItem.
The CategoriesMenuItem will have to display one of the 3 SVG react components (Weapon, Shield, Armor) and call the setPage from earlier on click. In the image, the first icon is selected and has different style applied.
Solution
Adding animated arrow to navigate
We are now going to provide a second way to navigate between the pages with arrows at the edge of the items grid component.
This time instead of calling setPage directly, we'll create a navigateToDirection function which should either take 1 or -1 to represent the direction and then call setPage with the appropriate value.
These arrows should:
- call navigateToDirection
- be animated on the scale property to create a zoom in/out effect with framer motion
- be disabled if reaching either max/min page.
We'll also have to update our useState inside App to store both the page and the direction.
Notes
The arrow image is available under the asset folder under the name arrow-no-curve.png. We may have to move it to the public folder to use a static url directly.
We only have one arrow as an image, so we need to rotate the image to create the left arrow.
Solution
Handling keyboard to navigate between page
We are now going to provide a third way to navigate between the pages ! Triforce requirement, we have no choice.
For this step we have to change the handleKeyPressed and goLeft / goRight functions we wrote in part 1.
- handleKeyPressed: we need to call navigateToDirection inside ArrowLeft and ArrowRight event when we are at the edges of the ItemsGrid
- goLeft / goRight: we loop over horizontal cells when we reach the edges of the ItemGrid.
Notes
We can optionally refactor the if/else inside the handleKeyPressed function by a switch block.
Solution
Adding animation on page change
For the last part we are going to add an animation to the page transition.
The animation will be added to the ItemsGrid component. We want to slide the ItemsGrid from the right or from the left depending on the direction that we stored in our useState.
To create this animation we are going to use the variant api from framer-motion, it allows us to define the different states of our animation in an object. In this object we will have two states:
- enter: this is the starting position, here we will slide the ItemsGrid to either -100 or 100 on the x axis and apply an opacity to 0.
- center: here we will move the ItemsGrid back to the center and reveal the ItemsGrid at the same time with opacity to 1.
To sum up we are going to give our motion.div a variant object with two entries enter and center. Each entry will define values for x and opacity properties. It should look like this:
const variants = {
enter: (direction: number) => {
return {
x: ??,
opacity: ??,
};
},
center: {
x: ??,
opacity: ??,
},
};
We'll also need to define the transition we want for these properties, we can use the values below:
transition={{
x: { type: "tween" },
opacity: { duration: 0.2 },
}}
In the end our motion.div component inside ItemsGrid should have the following props:
<motion.div
custom={direction}
variants={variants}
initial="enter"
animate="center"
transition={{...}}
/>
Since there is no direction involved when we change pages using the CategoriesMenu we should probably just animate the opacity in this case.
Ressources
Solution
What's next
Congratulations for completing part 2 we've made a lot of progress on completing our demo.
In part 3 we will add the final touch to bring this interface to life !
Again feel free to share any feedback and I hope you had fun.
Top comments (5)
Amazing job, I'm learning a lot! Thank you!
Amazing!! Canβt wait to dive into this, Florent! Awesome job and thank you!
Where is the demo?
It is hosted here: gameuionweb.com/zelda-botw/inventory
π₯π₯π₯ amazing!