Build a Reusable Component in React (Shadcn/ui, Tailwind)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
In this video, we will be building a reusable,  extensible, and production-ready UI component,   similar to Shadcn/ui. We're going to be working  with Tailwind, Class Variance Authority, and CLSX.   And also, by the way, this is exactly what I've  done for all of the UI components in my upcoming   React course, Project React. If you're interested,  it's the first link in the description. So over   here, I have a project that is created and  running with Vite, and we're going to be   creating a component inside of the components  slash UI folder over here. This folder is meant   to hold any React component that is pure UI,  right? That means a simple component that usually   doesn't have any state, doesn't do any complex  logic, and only renders simple UI, for example,   a button component or an input component. For  this video, we're going to be building a button   component that is very similar to Shadcn/ui. In  fact, it's actually going to be a simpler version   of their button component. This will then allow  you to build your own components just like this in   your project. Or if you want, you can go and use  the ones from Shadcn/ui and be able to easily edit   and understand them after watching this video.  You're going to find a link to this repository   that is going to contain all of the code that  I show in the video in the description. This is   also going to have all of the dependencies with  their correct versions, the ones that I used for   this video. So first, let's start by creating a  new file inside of components slash UI. So we'll   come here, we'll right click new file, and we're  going to do button.tsx. Then we can close this for   now to make a little bit more space. And inside of  here, we can create a new React component. So we   can do our GSFC. This is a snippet that I have,  if you're curious, I have a video on my VS code   setup that you can go and watch. This component is  going to be called button like this. And for now,   it's not going to return anything, then  we're going to come here at the top,   make some space. And now we're going to start  defining the props of this component because we   are working in TypeScript. So I'm going to do type  button props. And this will be a type in here,   we actually want to give this button our own  custom button component, we want to give it all   of the props, all of the attributes that you could  give to otherwise a normal HTML button element.   And in React, to do this, we can use react dot  HTML attributes. And then here we can pass HTML   button element like this, this is going to make  these button props here be exactly everything as   the HTML button element props that we could  pass if we were to just use a normal button.   So then we can come here to the button, we can do  props, and then we can type these as button props   just like this. And then we can come here and just  return a button like this, and then pass the props   over here. So what we're doing here is we're  taking all of these props from this component,   and we're just spreading them to the HTML button,  which means that whatever we pass here, when we're   actually using the button component, will also  go to this HTML button. That's it, we're just   using the normal HTML button, but we're wrapping  it in our own custom button. And with this, we now   have a fully working button custom component that  we can use anywhere in our app. So that means we   can come here, we can find our app dot TSX file,  we can open this. And we can replace all of this   here, which has calls and solutions, which I put  here as a reminder to say that, hey, did you know   that over 50% of you watching this video are not  subscribed, which honestly is unfortunate, because   you're getting a ton of value here. And I really  think that that deserves a subscribe button. So if   you haven't subscribed, the button is right there,  and it's free. So now that we are subscribed,   hopefully, we can remove all of this and we can do  button and then import our button from components   UI slash button. And then we can call it like  this. And we can pass it some text inside. For   example, we can do click me like this. If we now  open up our browser, we can see our button here   on the screen, if I can make it a little bit  bigger so that you can see this is our button,   this works, our cursor changes to a pointer when  we hover over it, which is the default behavior   of a button. This works exactly as we expect.  Now the next step for us to do is, of course,   to add some styles, because currently, this button  looks a little bit boring. Now to add styles to   this button component, we're going to be using the  class variants authority library, this will allow   us to configure different variants for a button,  each with its own style. This will make our button   reusable and have different styles easily based  on a single prop. The first thing that we want to   do is we want to come here to button props. And we  want to add another prop that is not part of these   props here, the HTML attributes of a button,  we want to add something that doesn't belong   in there. What we're going to do is we're going  to do the variant like this. And this is going   to be the different variants that our button can  have. And we're free to configure this in any way   that we want. For this video, we're going to keep  things simple, we're just going to do two options,   it's going to be primary, or it's going to  be secondary like this. So our button can   only be of type primary, a variant primary, or a  variant secondary, then we're going to come here   below the button component. And we're going to  define something new, we're going to do const,   and then button variants. And that is going to be  equal to a function, which we're going to do CVA.   And that is going to come from class variants  authority. This is a function that returns a   function. So this button variant, it's highlighted  here as a function, this function will return a   string that we can use in the class name property  of this button HTML element. This CVA function   over here comes from class variants authority.  And it essentially allows us to set some default   styles for all of our buttons, regardless of their  variants, and then apply some variant specific   styles to our button. So if our button is a  variant primary, we're going to apply some styles.   If our button is a variant secondary, we're going  to apply other styles. And we can override the   default styles based on the variant that we pass  as a prop. So first, let's start by adding the   default styles. These are the styles that will  apply to any button regardless of its variant.   So I'm going to pass here as the first argument  a string, these will be the default styles.   And we're first going to add some padding to the  button. So we're going to do padding y of two, and   then padding x of four, then we're going to make  this button a little bit rounded. So we can do   rounded MD, then we're going to want to add font,  semi bold to make the text inside of the button a   little bit thicker. And finally, we're going to  add a hover state. So we're going to do a hover.   And then if the button is currently being hovered  over, we're going to do opacity. And then 15. Just   like this, this is simple enough, but it's  going to get the job done for what we want   in this video. Then as the second argument to this  function, we can pass our variant specific styles.   So we're going to pass here an object. And then  we're going to do variants, like so variants. And   this is going to be all of the different variants  that we can configure for this specific button,   right. So this variance over here comes from CVA,  you cannot define how this key is named over here.   But this is going to be an object. And inside of  here, you can do anything that you want. In our   case, because we have a variant property here that  can be either primary or secondary, we're going   to put variant over here. So very end like this.  And this is going to be an object as well. And now   we're going to give both of our keys primary and  secondary. So we can do primary, that is going to   be an empty string for now. And then secondary is  going to also be an empty string like this. With   this, we've now passed an object as our second  argument to CVA, this object has the variants key,   which comes from CVA, you cannot control this.  But then we provided a variant property here,   which we can use to determine if it's secondary,  put these styles, if it's if it's primary, sorry,   put these styles, if it's secondary, put these  styles. And this you're going to see when we   actually use this button variants over here,  because this is a function, and we're going to   be able to pass this variant prop from the props  of this component to this function. And that is   going to select the appropriate styles and merge  them with these styles over here, and then style   our button correctly based on the variant. So  let's now start defining our styles. So we're   going to come here and in primary, we're going to  do background gradient to right, so a gradient to   the right from and we're going to do primary 500.  This is a color that I have set up in my tailwind   config, then to primary 700. This is also another  color that I set up in my config. And these are   the basic styles of my traditional button of  the color palette of cause and solutions. And   then we're also going to do text black, because  the text should be black on a background color   like this. Then in secondary, we're going to keep  things a little bit simple, we're just going to do   background grayscale, and then 700. And we're  going to do text white, because in this case,   the text should be white, then the next thing  that we want to do is actually add a default   variant in case one isn't provided. Because the  thing is, we don't want to have to provide this   variant property every single time. Actually, we  should make this optional. Because if this isn't   provided, we should just assume that the button  is going to be a primary variant, because that's   most likely what you're going to want in your  project. So this makes sense. And then with this,   we can come here and at the same level as this  top level variants over here, right below it,   we can define default variants and pass this as an  object. And then we can do variant like this. And   this is now properly typed, as you can see here,  because we have defined variant over here, that's   where this is coming from, I can set for variant,  I can do primary, it knows exactly the values that   I have available, because again, they are coming  from this object over here, we define variant,   and then we define primary and secondary. So these  are valid options that we can use. And with this,   now we can actually use this button variants  over here in the class name prop of this button,   because everything else is configured. So  we can come here and we can do class name,   name like this. And we can pass it here, we can do  button variants, this is a function that we call,   so we're going to call this function. And then we  have to pass over here, we have to pass a variant,   which can either be primary or secondary. So this  we're going to have to access it from the props of   this component. So what we're going to do here is  we're going to destructure this. And we're going   to do variant like this. And then the rest of the  props, we're just going to do dot dot dot props   like this, because we don't want to destructure  every single prop, we just want to goAnd lastly,   we have a button variant. So, we have the style,  and we have the style. We can group them in this   props object here, and still spread them exactly  like we had it before. So, then, now that we have   this variant here, we can come here and pass it to  button variants. We can do variants like this. And   now, the variant that we pass from the props of  this component is going to get sent to the button   variant, which is then going to configure  the specific styles of that variant. Again,   if it's primary, it's going to take these styles,  and it's going to merge them with these styles,   it's only going to take the styles that we  have here. That's how this works. And now,   if we go back to our browser and open up the  application, we can see that our button now   has the updated styles. These are the primary  styles of the primary variant. That's the gradient   background that we configured here. And as you  can see, in our app component, this is how the   button is used. We haven't provided a variant, so  it means that it's being defaulted to the primary   variant, which actually means that we can come  back here to app, and if we wanted to set this on   a different variant for this specific page, all  that we have to do is come here and do variant,   and then we can do secondary like this. And now,  just with this, we have our button over here that   completely changes styles by a single prop. Now,  the cool thing about this is that the button is   still rounded. The button is still reducing  opacity when we hover over it. Everything   else that we configured to apply to all buttons  is still being applied. The only thing that is   different is the styles specifically to a variant  that we've configured in this object over here   in this one here in this primary and secondary  object. Only the styles that we configured here   are specific to a variant. Everything else applies  regardless of the variant. And one thing to note,   I did mention this, but it's worth rementioning  here. We could have called this any way that we   want. This could have been size. This could have  been any other property. We could have passed this   any other values. All that we would have to do is  just configure those same values over here, and   it would work exactly as we expect exactly in the  same way. We would just have to pass that property   here and everything would work right. So you're  completely free to customize this in any way that   you want. And oftentimes, if you're working in a  real world project, you're going to have a design   system that you have to adhere to. And you're just  going to want to implement that design system in   these props of this component. And it's going to  work and it's going to be super, super efficient.   Now, there's one more thing that we want to  do here to improve this component a little   bit better. We also want to make this component  be extensible so that if this app component here   wants to apply some custom styles that does not  belong to any variant that are perhaps just one of   styles for this one specific page or component, we  want to allow this component to the app components   to configure that on the button and overwrite  any specific styles that we set in this button   variants object. And so the way that we're going  to do that is we're also going to come here and   we're going to access the class name property of  the button props that again comes directly from   these props over here. Every button element in  React will have access to a class name that we can   destructure over here. And then what we're going  to want to do is we're going to also want to take   into account this class name property along with  the button variants and merge these two together   and use the class name to overwrite anything in  button variants. And the way that we're going to   do that is actually super simple. We're going to  have to create our own utility function that we're   going to use to merge these two things together.  So what I'm going to do, I'm going to come in here   inside of the utils folder, and I'm going to  create a new file and I'm going to call this   cn.ts. I'm going to close this and here I'm going  to define a function, export function, cn.ts, I   mean not .ts, the file is .ts, but the function is  called cn. Cn is short for class name, that's why   this is called it this way. This function is going  to use two things. It's going to use the tailwind   merge function, twmerge from tailwind merge, which  is going to safely merge all of our classes here.   Because in tailwind, the order of the classes  matter and you want to be able to safely merge   them because if you don't, you might have some  unexpected results and some styles that you think   are being applied are actually not being applied  because they're not implied in the order that you   expect. The second thing that we're going to have  to use is a thing called clsx. This is a library   that allows you to pass multiple things and  convert all of those to a string that ultimately   results in the class name of a specific element.  We're going to go over this as we're using these,   so don't worry, this is going to make sense. So  first, we're going to define some arguments to the   cn function over here. So we're going to do dot  dot dot and then inputs like this. These are going   to be all of the potential class name inputs that  this function can receive, and we can have any   arbitrary amount of them, we don't care how many  there are. These inputs are directly going to be   sent to clsx, so we don't even have to manage them  manually ourselves. So what we're going to do is   we're going to type these and this is going to be  class value from clsx, as you can see over here.   We're going to make this an array of class value.  If we go here to the definition of class value,   this can either be a class array, so an array  of classes of strings, or a class dictionary.   So this is here a record with the classes as keys  and then any value as the value of the class. Then   it can also be a string, a number, null, boolean,  undefined, anything that we want. This is clsx,   and it's going to use this to actually return  everything that you pass into one single string   that we can use as the class name property. So  then we're going to take this string, we're going   to take all of these inputs here, we're going to  return, and we're going to do twmerge. This is   the function from Tailwind Merge, which is just  going to merge a string of classes, of Tailwind   utility classes, and make sure that there's no  duplicates and make sure that we can safely use   that on the class name property. And then we're  just going to pass this, we're going to do clsx,   we're going to import this from clsx, and we're  going to pass the inputs that we received over   here. So we're not directly doing anything with  the inputs, we're just passing the inputs to clsx,   and then we're passing the result of that to  Tailwind Merge and returning that as the return   value of this function over here. Now, if you're  a little bit confused as to how this all works,   that's totally fine, I understand, you can go and  look at the documentation of clsx, see how that   works, and then look at Tailwind Merge and see  how that works as well. It's pretty simple. And   if you just look at a little bit, you'll get it,  don't worry about it. And then now that we have   this function here, all that we have to do is come  back here to our button and just use it. So we can   actually remove all of this button variants here.  And actually, let me just make sure that I copied   this correctly, we can just remove this and then  wrap everything in CN. So we can do CN and then   import this from utils slash CN. And then we're  going to call this and we're going to put here   the first input, this button variant, which is  going to be a string. And as the second input,   we're going to pass class name, like so. These  are the actual inputs to the CN function that   we defined here. So button variants with this is  going to be a string that is going to be the first   input. Class name is going to be a string that  is going to be the second input. This is going   to come here and it's going to resolve and merge  this with Tailwind Merge. And that is going to   return to us one single class name string that  we're passing to the button class name. So now   if we open up the application, the button looks  exactly as we had it before. But now we can come   here and we can go to app and we can provide any  class name that we want. So we can do class name   and we can do, for example, background red 500 and  set a background color that is going to override   the background color of the variant of type  secondary. Now, if we open up the application,   our button has a background color of red, which  is exactly what we specified. And this is a   great way to build really extensible and really  composable components that you can use across   your entire application. And this is exactly what  Shadcn does with all of their components. This is   literally the same thing that we did here. The  only difference is that in Shadcn, they usually   have a lot more classes, and it's a little bit  more overwhelming to read and understand. But   this is really simple. And again, it's exactly the  same thing that Shadcn does. And we're using the   same libraries in the same way and producing the  same output, just in a simpler way that is easier,   perhaps for a beginner to understand and to work  with. So if you're interested to learn more about   this and see how Shadcn works, you can go to their  website, UI dot Shadcn dot com and see how they   build their components and actually integrate them  in your own applications. And if you want to see   more examples of components just like these,  I would strongly recommend that you check out   my upcoming React course, Project React. You can  find it at cosden.solutions/project-react, or it's   going to be the first link in the description. As  I said in the beginning of this video, all of the   UI components that I built in this project in  Project React all followed the same exact thing   that we did in this video. So these are a great  example if you're trying to learn React. And this   course is going to teach you literally everything  that you need to know to be able to build a real   world, big and complex application with React. You  will be guided step by step, every single step of   the way with code and videos. And by the end of  it, you'll be able to confidently, meaningfully   contribute to any React project that you work on.  So again, cosden.solutions/project-react or click   the first link in the description. You will  not regret it. There is no other course like   it. I promise you. If you enjoyed this video and  you want to see more videos just like this one,   make sure to leave this video a big thumbs up.  You can also click here to subscribe or you can   click here to watch a different video of mine  that YouTube seems to think that you're really   going to enjoy. And with that being said, my  name has been Darius Cosden. This is Cosden   Solutions. Thank you so much for watching and  I will see you in the next video. Ciao, ciao.
Info
Channel: Cosden Solutions
Views: 13,743
Rating: undefined out of 5
Keywords: react tutorial, react crash course, react developer, learn react, react hook, react hooks, react hooks tutorial, programming tutorial, react hooks explained, computer science, tutorial for beginners, react component, learn programming, web development, frontend development, coding for beginners, simple code, easy programming, react, react native, tailwind, tailwind css, shadcn ui, shadcn, clsx, class-variance-authority
Id: Gp95bKdmfok
Channel Id: undefined
Length: 19min 8sec (1148 seconds)
Published: Wed Mar 13 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.