DEV Community

Cover image for Creating an Expandable Card in React
Nolan Miller
Nolan Miller

Posted on • Updated on

Creating an Expandable Card in React

Thanks for joining me today, if this is your first read, welcome! In this series I’m documenting the entire process of creating a full-stack progressive web app called Roast, that helps me roast my coffee!

Find out more about the project in the Day 1 log!

I mentioned yesterday that I had some Home page styling that wasn’t acting right, and it turns out I left a 40px bottom margin on one of my elements by mistake! So, with that removed, it’s all tidyed up!

You may also notice that I made the icons in the footer a bit smaller, which I think makes for a more balanced look overall!

Check out this short video of the UI!

Creating a Card Component

My work today has focused on creating the expanded version of the collapsible component. This presented a bit of a unique challenge. I would like the card itself to expand from where it is on screen into the location that it sits in its expanded state, but, after struggling to get the card to lay on top of the rest of the UI already, I thought that today was not the best day to tackle just a small detail.

Here’s what the expanded card looks like right now!

Expanded Card Component

I’m happy with how it came out! You can open this by clicking on the card component. When it’s active state is triggered the following JSX is returned by the component:

return (
  <div className="expanded-card-container">
            <div className="Card expanded">
                    <div className="title-bar">
                        <h2>{name}</h2>
                        <div className="favorite-container" onClick={handleHeartClick}>
                            { isFavorite 
                                ? <HeartSolid />
                                : <Heart />
                            }
                        </div>
                    </div>
                    <hr/>
                    <div className="expanded-body">
                        <div className="expanded-description">
                            {/* TODO: Create a rating component, pass down rating and return stars with the right value */}
                            <p>Roasted on {dateFormat(dateRoasted, "mmmm d, yyyy")}</p>
                            <p>{roastString(percentLoss)} ({percentLoss}%)</p>
                            <p>{roastedOz} oz</p>
                        </div>
                        <div className="roast-details-expanded">
                            <h3>Roast Details</h3>
                            <table>
                                <tr>
                                <td class="label">Starting weight:</td> 
                                <td class="value left">{startingOz}oz</td>
                                <td class="label right">Roasted weight:</td>
                                <td class="value">{roastedOz}oz</td>
                                </tr>
                            </table>
                            <hr/>
                            <table>
                                <tr>
                                    <td class="label">% weight lost:</td>
                                    <td class="value left">{percentLoss}%</td>
                                    <td class="label right">First crack:</td>
                                    <td class="value right">{firstCrack}</td>
                                </tr>
                                <tr>
                                    <td class="label">Heat level:</td>
                                    <td class="value left">{heatLevel}</td>
                                    <td class="label right">Temp Rise:</td>
                                    <td class="value right">{tempRise}</td>
                                </tr>
                                <tr>
                                    <td class="label">Max temp:</td>
                                    <td class="value left">{startTempF}°F</td>
                                    <td class="label right">Opened lid:</td>
                                    <td class="value right">{openedLid}</td>
                                </tr>
                                <tr>
                                    <td class="label">Min temp:</td>
                                    <td class="value left">{lowestTempF}°F</td>
                                    <td class="label right">Heat off:</td>
                                    <td class="value right">{heatOff}</td>
                                </tr>
                            </table>
                            <hr/>
                            <table class="summary">
                                <tr>
                                    <td class="label final">Total roasting time:</td>
                                    <td class="value final">{dumped}</td>
                                </tr>
                            </table>
                        </div>
                        <div className="notes">
                        <div className="notes-head">
                            <h3>Notes</h3>
                            <PlusCircle />
                        </div>
                        <div className="notes-field">
                            <p>{notes}</p>
                        </div>
                        </div>
                    </div>

                    <Button 
                        color="var(--light-orange)" 
                        text="Close"
                        callback={collapse}
                    />
            </div>
        </div>
)
Enter fullscreen mode Exit fullscreen mode

It’s a solid block of code yeah? Let me break down what’s happening here.

First, I’m rendering a div that sits across the whole viewable part of the page. So, everything besides the footer. This sits above everything but the footer, so that there’s no interaction with the other elements that are underneath this one.

Then, there’s the card component! This takes in the same roast object as the collapsed card, because it's the same component!

After some details that I had to create a few utitilty functions to create dynamic strings, is the Roast Details section! I was back and forth between using CSS grid and using an HTML table for this. I think that the table was more simplified and is clearer for a programmer to read. However, I think that semantically, it leaves a bit to be desired. This is something to come back and clean up, to make sure that I have headers in the correct spots and I’m identifying which values belong to what.

Then there is a notes section. This isn’t a form yet, I’d like to use the plus circle component as a trigger to open the textarea for editing.

The last component is the Button component that I created yesterday, I did end up adding a callback prop to it so that I can pass something to the onClick attribute of the div!

Tidbits

I didn’t like the loop of my installed application because of the top bar on my iPhone, where my service and WiFi indicator are. I am using a very dark gray, rather than a black for this background and when it stood against that status bar it was very stark.

It turns out, that’s not hard to change. In a meta tag, you can just add the background color, and then it will render out just fine!

<meta name="theme-color" content="#202020" />
Enter fullscreen mode Exit fullscreen mode

Check Out the Repo

If you want to keep up with the changes, fork and run locally, or even suggest code changes, here’s a link to the GitHub repo!

https://github.com/nmiller15/roast

Top comments (0)