تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Introducing cn utility0:00

Okay, we're going to begin our exploration by studying the little cn function that you can see here. So it's used everywhere in SHA CN UI and it's important that you understand what it does and how to use it. Okay, so if I go in the Button components that we've generated in the previous lesson, you can see that it does import the cn function from the uts. And then here where the component is being returned, you will find the cn function here,

And then here where the component is being returned, you will find the class function here, wrapping this button variant, which we'll talk about later. But note that there is one of the things inside that function is the class name. And that class name here is the class name that is passed to the component as you can see up here. So when you use the button component and you pass a class name, it's going to end up being here and mixed with the class function.

Using cn in Button0:48

and you pass a class name, it's going to end up being here and mixed with the CN function. To help us understand how the CN function works, why don't we try to consume, use the Button component, we've generated it and now we can go and use it in our welcome page. So let's go in the welcome.tsx file, and as a reminder, this is what it currently looks like and we're going to change the markup a little bit. So I'll have a wrapping div with some padding

and we're going to change the markup a little bit. So I'll have a wrapping div with some padding so it's not flush against the edges of the viewport. And then let's have a flex container with a gap of four. And inside of there we're going to consume our Button component. That should show up in the auto complete. Yes, from App\Components\UI\Button. So it's going to auto import that, and the text can be just button fairly boring, I know.

So it's going to auto import that, and the text can be just button fairly boring, I know. And let's have two of these buttons. Okay, and here's your first chat here, UI component in action. Now remember that the button component was allowed to receive a className prop. And so here on the second one, why don't we pass a className of uppercase and who would've guessed it?

why don't we pass a class name of Uppercase and who would've guessed it? The second button is now Uppercase. And in a nutshell, that's the CN function at work. It takes the classes from the button and the classes pass to the button and then merges them, mixes them together, but there's a little bit more to it than it may appear. So why don't we try recreate the CN function from scratch to understand exactly how it behaves.

Building Box demo2:04

So why don't we try recreate the CN function from scratch to understand exactly how it behaves. Okay, so I'm going to create a new component. This is not a shat and UI component, I'm just doing a demo. So let's have a function called box and it's going to return some JSX. So it's going to be a div that has a background of white, a padding of eight and maybe rounded lg. And inside of it, it's going to accept children. So we need to receive the children here.

And inside of it, it's going to accept children. So we need to receive the children here. And yes, thank you copilot. We are going to type this ReactNode, but let's import ReactNode directly. So that's our Box component. It's basically a wrapping div that has some styling. Oh, and maybe let's add a shadow-md. And because I have a white background here, I think that on the pageWrapper here, I will add a bg of

And because I have a white background here, I think that on the page wrapper here, I will add a bg-slate-300. That should be dark enough for the video recording. And so instead of the two buttons here, I will use two boxes, a box with a <p> tag that says I am inside a box. And we'll duplicate that. And we have our two wonderfully looking boxes. Let's make the background full heights though. So here I'll add a min-h-screen like so and so.

Let's make the background full heights though. So here I'll add a min-h-screen like so and so. Now, just like we've done with the buttons, I'm going to try to add some classes to the second box and see what happens. Alright, so let's go in the second box and go class name equals and let's go bg-yellow-300 and maybe remove the rounded corners with rounded-none. All right, let's go take a look and well, nothing has changed.

All right, let's go take a look and well, nothing has changed. You have a little hint here. You can see TypeScript is not happy because the class name property does not exist on our Box components. So if I scroll down here, the Box component only expects one single prop children and nothing else is meant to be passed to it. And if you pass to it, well nothing's gonna happen with it.

Spreading div props4:00

and nothing else is meant to be passed to it. And if you pass to it, well nothing's gonna happen with it because it's just going to throw it away. So we need to be a little bit more permissive with our boxProps. And so let's create a type here, actually type boxProps. And so it's going to have children ReactNode, but we also going to allow any attributes that you could pass to a div element because it returns a div.

that you could pass to a div element because it returns a div. And we can do this with component props, which is a type from React and then pass the DASD element. And so now I'm going to replace the type assignment here to the box props. And now it's going to allow children and any other HTML attribute or prop that you want. And so we need to receive these and because there is a multiple amount of props available,

And so we need to receive these and because there is a multiple amount of props available, I will spread and it's a common pattern to call it restProps. And it's basically everything except what's been specifically structured. And now we want to pass these restProps to our component and I can once again spread them here like so. Dot, dot, restProps. So now our component receives children and uses it here.

Dot, dot, rest props. So now our component receives children and uses it here and then spreads anything else that is passed to it after it here we're going to take a look at the two boxes in the ui, but have a guess at what the second box is going to look like now. Huh? Is this what you were thinking of? It looks like we have lost most of the stars of the default box.

It looks like we have lost most of the stars of the default box and we only have the classes passed through this box, which was bg-yellow and rounded-none. And if you wonder why we still have the padding, well this is not padding, it's because of the flex parent. But if up here I was adding the items-start class, you'd have a fairer representation of what this actual box looks like. So we passing rounded-none

what this actual box looks like. So we passing rounded none and bg-yellow-300 as a class name attribute or prop. And then it's received here within the component props, it's grabbed in the restProps and it's spread out here. So part of this spread here is the class name attributes. So it's literally redefining the class name attributes and passing restProps that class name. And so you can clearly see that there's a problem. The class name attribute is defined

And so you can clearly see that there's a problem. The class name attribute is defined twice with the same name. And so what's going to happen is the second one, the last one is going to completely wipe and override the first one. In other words, all of these classes will be thrown away and only the classes passed here, which are these two will remain. And so that's how you end up with the box looking like this.

which are these two will remain. And so that's how you end up with the box looking like this. And now, because we haven't passed any class name to the first one, it is completely unstyled. Alright, so let's go and delete our class name. And now I'm going to take all of the rest props and I'm going to place them before the class name attribute. So keep in mind that this contains the class names passed from the component here, but then it gets overwritten with these this time.

contains the class names past from the component here, but then it gets overwritten with these this time. I'm pretty sure that you know exactly how the second box is going to look like, don't you? Yep. The second box looks exactly like the first one because we've completely thrown away the classes past from the box components. Okay, so it looks like we need to find a way to merge the two class name values, the class names from the components definition,

Merging className safely7:14

to merge the two class name values, the class names from the components definition, and then the class name that is passed when consuming the components. So instead of having the rest props here, why don't we get a little bit more specific and accept a class name optional string in our props. And here I will also accept it class name. And because it's optional, let's default it.

And here I will also accept class name. And because it's optional, let's default it to an empty string. So now, rest props becomes everything except children and class name. And now, well, I'm going to turn this into a template string so I can at the end add the class name prop. So now the Box component defines its own base classes here and then allows the consumer to pass extra classes and adds them at the end of the string here.

and then allows the consumer to pass extra classes and adds them at the end of the string here. And by the way, let me move the restProps at the end. It's a little bit more common practice. And now since classNames is not part of restProps anymore, we are not going to override anything with that. So let's go take a look at the browser. But this time I have a good feeling and here we go. This time we have the results that we were hoping for. The second box has all the styles from the first box,

This time we have the results that we were hoping for. The second box has all the styles from the first box, the shadow, the padding, but we have overridden the background color and the border radius. So this is a valid approach to merging classes from the component's definition and the component consumption. Uh, and for basic jobs, it's going to do the trick. If we wanted to get picky though, let's open the dev tools,

Uh, and for basic jobs, it's going to do the trick. If we wanted to get picky though, let's open the dev tools, not the styles, but the actual rendered HTML. And if I make the sidebar a little bit bigger, you can see that the first one where we have not passed any classes has an empty space here at the end. And it's because in our template string here, we have an empty space. And then if the class name is empty, it's an empty string.

we have an empty space. And then if the class name is empty, it's an empty string. So the template string is going to end up with an empty space. That doesn't really matter honestly. But another approach that you may have seen in the wild is a little utility function. Uh, let's call this one cx for class merging. And it's going to receive an unknown amount of classes,

Uh, let's call this one CX for class merging. And it's going to receive an unknown amount of classes, which is an array of strings. Yes. And then, uh, I'm gonna take this answer. It's going to join the array of strings with a space between each. You can see the added filter boolean here, which will remove any faulty values. So in a sense, this CX function does the same job as what we've done manually here,

So in a sense, this CX function does the same job as what we've done manually here, but it's a little step up in terms of functionality because it can receive multiple array of strings. And if you had five, six, or seven strings to merge together, this approach would become a bit messy. So let's try here now to use the CX function. And so our first string is going to be that one and then comma separated.

And so our first string is going to be that one and then comma separated. We are going to have class name as the second string. And so essentially this is going to do the exact same. It's going to merge these classes with these classes passed to the box. And so as a result, the classes are merged properly. And this time the first box here where we didn't pass any classes doesn't have an empty space at the end.

where we didn't pass any classes doesn't have an empty space at the end. This however, will only work with array of strings. And at this point, if you're coming from the view world, you probably laughing because out of the box view has that concept of class binding where you can have objects, array, conditional, and all sorts of scenarios within classes. And then view is going to understand how to reconcile these classes.

clsx and tailwind-merge10:37

And then view is going to understand how to reconcile these classes. So there is no such thing out of the box in React. And here we sort of started creating our own little version of it, but we are going to step it up with a library called CLSX. As you can see, CLSX is pretty darn popular. And what it allows you to do is very similar to class bindings in view. So you can have all sorts of scenarios.

to class bindings in view. So you can have all sorts of scenarios where you have classes as strings, objects, arrays, and basically nested objects and whatever. And it's always going to be able to work out what the output of the combined classes should be. clsx is actually the first of the two libraries that the cn function uses. And so it's already installed as a dependency when you use Chat n ui.

And so it's already installed as a dependency when you use Chat n ui. So here we can simply try to replace our CX function that we manually created here with CLSX. You can see the auto import available. And so now I'm going to delete this helper function that we created manually. And the component should behave exactly the same. CLSX is going to combine the string here with the class names passed to the box

CLSX is going to combine the string here with the class names passed to the box and yep, it's working as expected. But now with CLSX, you can do much more advanced things. So check this out. Up in the Welcome page component, I'm going to add a little bit of state const, toggle and setToggle. We're going to use React, useState and set the default value to false. And here above the two boxes, I'm going to bring back one

and set the default value to false. And here above the two boxes, I'm going to bring back one of the SHA cian button components. So the text's going to say toggle. And here we are going to add an onClick and unClick. We are going to set toggle to the opposite of toggle. And maybe to be a bit more explicit, I will output also the value of toggle. So if it's true, it's going to be on and if it's false, it's going to be off.

So if it's true, it's going to be on and if it's fall, it's going to be off. And let's just add empty four before the boxes. So everything looks nice. So when we click on the button, the value of toggle toggles between false and true, let's try it out. Yep, it works. And so now we're going to use the toggle value to change a few things on the box. So down here on the second box, let me scroll down a bit. I'm going to make use of the clsx function.

So down here on the second box, let me scroll down a bit. I'm going to make use of the CLSX function. And so we'll pass the two classes first. And then this time let's have an object and we're going to have the class of uppercase if the toggle is true. So on the left hand side of the object here is the class name uppercase. That's going to be output if the value here is true, and so as I change the toggle, I'm expecting this

That's going to be output if the value here is through the, and so as I change the toggle, I'm expecting this to go uppercase and yep, that works. And with the dev tools open, you will see the uppercase class being added and removed from iv. Alright, CLSX made our class mixing a lot more powerful, but there's still a subtle but really important problem. Check this out. Instead of the uppercase class, when the toggle is on, I will this time have rounded-full.

Check this out. Instead of the uppercase class, when the toggle is on, I will this time have rounded-full. So we're going to change the border radius of the box and let me remove the rounded-none class here. So we are not competing against ourselves. And so I'm expecting the box to be bg-yellow-300. And then when the toggle is on the border radius should go to full. All right, let's try it out and toggle and oops. Uh oh, nothing is changing. Let's add one more thing.

All right, let's try it out and toggle and oops. Uh oh, nothing is changing. Let's add one more thing. Instead of always being yellow, we're going to depend on the toggle value to make the color either yellow or blue. If the toggle is on, the BGE is yellow, otherwise it's blue. And then if toggle is on, also we are going to have rounded-full. And honestly at this point with this syntax, we could add the rounded-full class in.

And honestly at this point with this syntax, we could add the rounded full class in. The toggle is on condition. And let's go one more time. Check out the ui. The toggle is on, the background is yellow, but the border radius is not rounded. And when the toggle goes off, the background is white. What? What's going on? It's almost like only a handful of classes are working and this is exactly what's happening. Well, that's annoying.

of classes are working and this is exactly what's happening. Well, that's annoying and that also feels a bit unpredictable. No, well, don't worry. This is where the second part of the CN function, tailwind-merge comes into play.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟