Now that we have our navbar finished I want to go ahead and create our collapsible sidebar. So let me go ahead and show you what I have running in my terminal. So I only have npm run dev running right now I'm not running any of my hooks, I'm not running ngrok or any local tunnel because I will not be creating or updating any of my users so I recommend that you don't do the same and instead I recommend that you do the same and just focus on creating the sidebar now. So let's go ahead and let's revisit our app folder, browse layout.tsx right here. So layout is the perfect place to also put a sidebar here.
And this is where I want to put it. I want to put it right above this children right here. So I'm going to go ahead and render this sidebar like this and when we save I'm going to get an error here that sidebar is not defined you can refresh the page if you didn't get the error. And now I'm going to go inside of my components here and I'm going to create a new folder sidebar like this. And then inside I will create a new file index.tsx and I'm going to export const sidebar and return a div sidebar.
Like that. Let's go back inside of our browse layout here and now we can import sidebar from not from Lucid React but from .slash component sidebar so the same way we did with navbar and now next to our homepage text here which is our children which is rendering the actual page which is here is also rendering the sidebar text next to it. So we're gonna go ahead and turn this into an actual component. So the first thing I want to do is replace this div with another component called wrapper. So I'm gonna go inside of the sidebar and create a new file of wrapper.tsx.
So wrapper is gonna be responsible for handling whether this sidebar is collapsed or expanded to its full width so let's go ahead and let's write interface wrapper props and I immediately wanted to receive children and that's going to be a type of react react node and the reason it needs children is because this is what it's going to look like so export cont wrapper and let's go ahead and pass in those children props here so let's assign wrapper props and let's get the children here and inside what we're gonna do is we're going to return an aside element and render the children inside like that so a very simple component and then we're gonna go back inside of the sidebar index here and instead of using this div we're gonna use that wrapper which we just created in the same folder level so right here and nothing should change but now we have this little component which takes care of collapsing our sidebar and inside of here we can handle anything else. And this will be a server component meaning that from here we're gonna be able to fetch our followed users, fetch recommended users and stuff like that because this is a server component so it has access to the database.
But our wrapper later is gonna be a client component, right? So it's gonna have use client here at the top. I'm gonna remove it for now. I just wanna give you an image here. And the reason it's gonna have use client is because it's gonna have access to a hook which will work with toStand which is gonna be responsible as kind of our global store, global state to detect whether it's collapsed or not And because we're passing the children inside and not direct components, we can also add another server component inside right later when we create that.
So that's how we inject a server component inside of what is going to be a client component. So that's what I was kind of trying to explain earlier if you remember where I stopped myself because I thought that it's getting too confused at that moment. So I hope by finishing this it's gonna get just a little bit clearer. So let's remove this and just write sidebar here. Make sure your wrapper looks exactly like this.
And let's go ahead and give this a side some class names. So let's write class name here. And I'm gonna go ahead and give it a fixed property. So as you can see now, when I added that, you can see how it kind of goes one above another. And now let's go ahead and give it a property of left zero.
So it's always on the left side. Let's give it flex, flex call. So all items go one below another. Let's give it a fix width of 60, an hfull property, a bg background property. And now as you can see it has completely hidden our text which was here which said homepage.
So it's still here, it's just underneath all of this stuff here. So let's do bg-background, border-right. So it has just a tiny little border so it's easier to distinguish where it ends. And let's also give this border a proper color of 2d2e35 like that and let's go ahead and give it a Z index of 50 like that. Great!
So now that we have this what I want to do is I want to install a package called zustand So I'm gonna go inside of my terminal here. I will shut down my app and run npm install zustand Like that. So let's go ahead and wait for this to install and let's do npm run dev. Perfect! So what I want to do now is create the store which is going to take care of whether this wrapper is expanded or collapsed.
So I'm going to close everything and I'm going to create a new folder called store. So I'm going to click on a random item here because I want to create that folder outside of everything so just create a new folder called a store like that and inside create a new file use sidebar dot TSX actually it can be dot TS because we're not gonna be doing any react inside so use dash sidebar dot TS And let's go ahead and import create from Sustand, which we just installed. Let's create an interface sidebar store. It's going to have a property collapsed, which is a boolean, so true or false. It's going to have a function on expand which is going to be a void and on collapse which is also going to be a void and now let's write export const useSidebar here to use that create which we imported from here and let's give this create an interface of sidebar store like that and then go ahead and open parentheses open parentheses again and write set and then go ahead and return an immediate object like this so make sure you wrap it inside of parentheses because if you did this it's not the same as doing this so this returns an immediate object and inside of this object let's give it some default states so collapsed by default is gonna be false on expand function is gonna be a function which calls a set which we extracted from the props here and what it sets is my apologies not like this so open a function like this and go ahead and set collapsed to be false and then below that let's do on collapse let's call set again collapsed true like that there we go so now we have our default state we have our onExpand and our onCollapse function right here so I want to go back inside of my wrapper now.
So let's go ahead inside of app folder, browse, components, sidebar, wrapper right here. And now we have to add that hook inside of here. So you can do that quite easily just by adding const collapsed and then use sidebar from store use sidebar which we just created. Let's get the state and let's just return all state here so we can properly destructure only the collapsed element inside. And make sure you do the export so you can actually import this.
And when you save, I believe that this is not going to work and in fact we're going to get an error. So let's refresh here and there we go. So useRef only works in client components and you can see that it exactly points to this hook which we just added inside of the wrapper. So that's what I said, that this wrapper component is gonna have to be a client component. So make sure you add useClient at the top of the wrapper here and then once we have this collapsed property we can dynamically change the width of this sidebar so by default it's 240 pixels wide and then what we're gonna do is dynamically change that if it is collapsed or not So in order to do that we have to import our cn package from libutils and now let's make this dynamic.
So I'm going to wrap the entire thing inside of curly brackets then I'm going to wrap the entire thing inside of the cn function and I'm just going to collapse this like that. So I want my default classes to be in the first line I added a little comma here and then I'm gonna write if we are collapsed in that case all I'm gonna do is change the width to be 70 pixels just like that And now you can see that it still looks the same. But if you go back to your user sidebar and let's change the default value of collapsed from false to true. And there we go. You can see how now it's just a small little sidebar here.
Even our underlying content is kind of sticking out. So that's what we achieved here. So bring this back to false and it should expand like this and leave it at false. And now what I want to do is go back inside of the index here and I want to go ahead and create a toggle element. So instead of this, I'm going to render toggle like this and if you save you're gonna get an error because it doesn't exist so let's go ahead inside of our sidebar here and create a new file toggle.tsx like this and let's export const toggle and let's return a div saying toggle like that let's go back inside of the index and import toggle from dot slash toggle and there we go now we have our toggle component here and here's the cool thing so remember how I told you that you can use console logs and look at where it logs to see whether something is a server component or a client component so this wrapper is a client component so you might be thinking alright everything we put inside is now a client component as well, right?
Well, that's not true because we're using the children method, which means that we can inject server components inside. So if I go ahead inside of this console here and add a console log, I am logged here like that. And if I look at my inspect element you can see that it's not being logged inside of the inspect element but instead it's being logged inside of my terminal right here meaning by that because of that children thing that we did right here this is still a server component. Right, so I just wanted to demonstrate that quick thing but it doesn't really... I could have chosen a bit of a better example because we are gonna turn this into a client component as well.
But I promise it's going to make sense once we add another component inside of this wrapper which is actually going to connect to our database to load the recommended users here and then you're going to see how useful server components actually are. But for now let's go ahead and let's develop this toggle component. So first things first, let's mark it as use client as well because it's going to have to access that use sidebar store, right? So let's go ahead and let's extract collapsed on expand and on collapse and on expand should be with a capital E and let's give it use sidebar, which you can import from store use sidebar. And in here, I wanna get the entire state and just return that state like this.
Great, And now let's write our dynamic label. So const label. If it is collapsed, in that case, we're going to tell the user expand, otherwise collapse. So we are doing, we are giving them an action to do. If we are collapsed, the action is gonna be, all right, you can expand this.
If we are not collapsed, in that case, the action is gonna be collapse this sidebar. So that's what we are storing here. So we can easily use it inside of here. So let's go ahead and change this from a div to a fragment like that. And first things first, let's go ahead and write if we're not collapsed.
So if we're not collapsed, make sure you add a little exclamation point at the end here. In that case, let's go ahead and render a div with a class name of padding 3, pl 6, margin bottom of 2, flex item center and full width. Let me just expand my screen here. And inside of here I want to write a paragraph for you. Like that.
And there we go. Now you should have a little text here which says for you. If you're not seeing that make sure that you did a proper ternary here with an exclamation point and make sure that your use sidebar has collapsed false. So this should be expanded, right? So let's go back inside of our toggle component where we added for you and let's give this paragraph some class names.
So class name for this one is going to be font-semi-bold and text-primary. Like that. There we go. Now it looks a bit nicer. And then what I want to do is add our button component, which you can import from components UI button.
And inside of here, I'm going to add arrow left from line, which you can import from Lucid React, as I just did right here. And I'm going to move that to the top. And let's go ahead and give this arrow left from line a class name of h-4 and w-4 and now let's go ahead and give this button a class name of h-auto padding to an ml-auto and then let's give it a variant of Ghost like this and there we go now we have a nice little toggle button here right so when we click on this what's supposed to happen is this goes to its collapsed state. And we can do that quite easily because we have the function right here on collapse. Right?
So let's go ahead and I'm just going to collapse all of the attributes which this button has and I'm going to add on click here to be on collapse. Like that. So let's take a look at that now. When I click on this, there we go. It's collapsed and you can see how that entire content now disappears.
And you can just refresh your page to bring it back to its original state. Great! So this should now be working for you. And now we have to create an alternative state which is when we are collapsed. So go ahead and click collapsed and make sure that all of your content in the sidebar disappears because above here we're gonna go ahead and write if we are collapsed in that case we're going to render something entirely different so create a div with a class name of hidden by default but only on flex is going on large devices is going to be visible using the flex Let's give it a full width, items center, justify center, padding top of 4 and margin bottom of 4.
Then inside just add a button which we already have imported and render arrow right from line. From Lucid React. So I believe this was arrow left from line and now this is arrow right from line like that. And now you should have a little button here. Let's go ahead and continue developing.
So I'm gonna go ahead and give this button a variant of Ghost a class name of H-AutoEmpadding2 like that and let's give this one a class name of H4NW-4 and it's not appearing for me maybe not even for you that's because on mobile devices we're not gonna allow the user to expand this sidebar. But if you expand your screen, there we go. Now you can see that you have a little button here which indicates that you can expand it back. But on mobile devices we're going to hide that button because I don't want users to be able to expand because they already have a small space to work with so I don't want to cause any more trouble with our space. And now let's go ahead and give this button.
So again I'm just gonna collapse all of my items here and let's give this button on click to be on expand. Like that. So expand your screen make sure you're in desktop mode and now you should be able to both collapse and expand your sidebar. But keep in mind that on mobile mode, you should not be seeing the button to expand it back. Right?
So also one more thing that we're gonna develop later is that when user goes from desktop to mobile mode this is going to automatically collapse so we're not going to leave them with this because then it's just weird that they can expand this on desktop then they go to mobile and it will automatically collapse for them so we're just gonna do all of that automatically for them. Great! So confirm that on desktop you can expand and collapse this item. Perfect! So now what I want to do is I want to create a little indicator that when user hovers on this it's like a little tooltip or a hint to tell them all right by clicking on this you will expand by clicking on this you will collapse and for that we're going to use this label right here.
We're going to create a component called a hint so we can wrap these buttons inside of that and then we're going to render this inside. So let's go inside of our terminal and let's add a package from chat.cnui called tooltip. So let's go ahead and make sure you add add I believe add tooltip. There we go. So make sure you add the add command as well.
Now it is installing our tooltip and you can do npm run dev again, refresh your localhost. And now let's go inside of the components folder alongside the theme provider, we're gonna create hint.tsx. And let's go ahead and let's import everything we need from s slash components ui tooltip which we just installed so we are going to need the tooltip itself we're going to need the tooltip content tooltip provider and tooltip trigger like this and let's write interface hint props to get the label which is a string children which are gonna be react react node as child which is gonna be an optional boolean side which is gonna be optional and have the properties of top, bottom, left and right and we're going to have a line which is also going to be optional and have the properties of start, center and end like that And now let's go ahead and let's write export const hint. Let's assign the props, hint props, and let's destructure all of the props inside. So label children as child side and align.
And then inside of here, let's go ahead and return our tool tip provider. And let's add tool tip itself inside. Let's give it the delay duration of zero. So I want it to immediately when the user hovers on this I want to show the tooltip and then let's add inside of the tooltip let's add a tooltip trigger and let's render the children inside and let's give it an optional prop as child to match our prop as child so as child prop is what we're going to enable whenever we are wrapping a button. So that way we're not going to have any hydration errors when it comes to that.
And now below the trigger add a tooltip content and inside we're going to render a paragraph which is going to render our label. So now what I wanna do is give this tooltip content a class name of TextBlack and BGWhite. And I wanna give it a side of side. So let me just collapse its attributes. And besides side, I also want to give it an align property of align so we can always modify it if it's going to the left and we want it to show on the right, for example, or add even a little bit of offset if we want.
And now let's style this class name here by giving it font semi bold. Like that. So that is our hint component. And now that we have this, make sure you save this file. We can go back inside of our sidebar toggle.tsx and let's go ahead and let's import hint from components.hint which we just created and first things first let's do this for the not collapsed state so this thing that I'm seeing here so just refresh your page and you should see that by default and wrap this button inside of the hint component like that and now go ahead and give it a label of label which we defined in this constant right here so make sure you have that and give it a side of right and give it a property as child because it is wrapping a button.
So now if you try you should have a nice little indicator to tell the user what this action does. And we can do the same thing for when we collapse. So I'm gonna wrap this button inside of the hint as well. And I'm gonna give it a property label of label. I'm going to give it side right and as child as well like that.
So expand on your desktop. Make sure you're testing this on the desktop mode. Now it says collapse and now it says expand. And one more time, if you're doing it on mobile, it's not gonna be visible. So that's not a bug, we're gonna take care of that.
Right now we're just focusing on the desktop mode. Great! So we finished this and now what I want to do is I want to ensure that the content which you can see that it's kind of peeking from here, it's hidden, right? Remember that text which we have, homepage? Also expands and collapses depending on the sidebar state so that's what we have to do next.
So we have to go back to our browse layout now so let's go ahead and go inside of app browse layout here and instead of just rendering our children like this I want to create a client component which is going to be a wrapper around our children so that way we can inject server components even though it's wrapped inside of a client component. So let's add container like this and wrap the children inside and if you save you're going to get an error. So inside of your components folder where you have the navbar and the sidebar just create a file container.tsx like that and let's go ahead and export const container let's return a div here and let's go ahead and give it an interface container props to accept the children which are react react node and inside of it let's assign the props container props and let's get the children like that and then we can simply render the children then when you have this go back to the layout and import container, not from Lucid React but from .slash components container, so you should have three imports, the navbar, the sidebar and the container.
And nothing should change right now, but let's go inside of the container here and let's give it access to our sidebar store so wrap this in use client and children are still server components because we are using that children injection method so make sure this is use client in the container here and then let's go ahead and add our use sidebar which you can import from store use sidebar and let's just give it all the state information and then inside of here let's get collapsed like that we're later gonna add two more elements here but for now I just want to do this so give this div a class name which is gonna be dynamic so we're going to import CN as well and we're gonna go ahead and first give it some default classes which is gonna be flex1 and then if it is collapsed it's gonna have a margin left of 70 pixels otherwise It's gonna have a margin left of 70 pixels on mobile, but on a large is gonna be is gonna have a margin left of 60 like this so go ahead and save that and let's take a look at this now and There we go.
You can see how this says homepage right now, but when I collapse, you can see how it has a smaller padding. And if I go here, you can see how it has that padding by default, right? And now just to wrap this entire thing up, we're gonna do that automatic collapsing on mobile mode. And I'm gonna explain why did I use this exact values here in a second. First thing I want you to do is go inside of your terminal and install a package usehooks-ts like this make sure you run npm run dev and make sure you restart your localhost, I mean refresh your page and then let's go ahead and let's import two things, so we're going to import useEffect from react and we're going to import use media query from use hooks DS and then in here let's go ahead and add const matches to be use media query and inside of it inside of here I want to add parentheses max width is going to be our LG breakpoint.
Our LG breakpoint is 1024 pixels. So we're going to match that here 1024 pixels. So this is going to be a JavaScript way for us to know whether we are in desktop mode or mobile mode. So what we're going to do now is add a little use effect here below this which is going to watch for that matches property and besides collapsed from this sidebar we're also going to need on collapse and on expand and then we're gonna write if it matches meaning that we are in desktop in mobile mode in that case let's go ahead and do on collapse automatically for the user else we're gonna do on expand and make sure you also add those two in here so on collapse and on expand like that And now if you look at it, see how it automatically collapses and expands right on desktop, we control that ourselves. So on desktop, we're going to allow the user whether they want it expanded or collapsed.
But on mobile, Regardless if they load the page initially on mobile mode or if they go from desktop to mobile we're gonna collapse it for them and we're not gonna allow them to expand it themselves. So you can see when I refresh You can see we're gonna improve this even further. Don't worry about that little blink. We're gonna fix that as well. But you can see how even if they go from loaded mobile and then expand the desktop we expand it to give the user a reminder, hey you can actually control these values.
And you can see how our content here is following the exact width of the sidebar so now I can explain those values here which I've written so margin left of 70 pixels when we are collapsed equals our sidebar wrapper here where we wrote that when we are collapsed the width of the sidebar is 70 pixels which means we have to push our content by 70 pixels and then so this is if we are collapsed. And then if we are not collapsed, we do kind of a weird thing, right? Because I, again, I write this, but why do I write this again? That's because, let me remind you, we have to check if we are on mobile. So even if we are technically expanded inside of our store and JavaScript, we are never going to render that.
And I believe we also have that here. So maybe we don't. All right. But we never wanted this to be loaded on mobile mode as ML60. I never want to push content that far away because I know that on mobile mode, regardless if this has been run or not, it's never gonna be able to be expanded, right?
But this is what should matter for you here ML60 on large mode which matches the width of 60 right here when the this element is not expanded which when it is expanded right great perfect so we have now our very intuitive and collapsible sidebar here it works great on mobile and what we're gonna do now is well kind of add some features here so that you can finally see those server components in action. So we're going to render the recommended users in here, which for now is basically just going to be a list of all of our users from our database. And then we're going to add some little loading skeletons, which are especially going to help with this. You can see when I refresh on mobile mode for a second it's expanded so it just looks weird. So we're gonna go ahead and fix that now.
Great great job!