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

Generating UI Table Component0:00

Let's talk about component composition in React. And for this we'll use the multi-part table component in shat N ui. Okay, so let's generate this table component with npx shatcn@latest add. And we will then go and find the table component right here. Alright, it's been generated and here's what it looks like. So you'll find out that there is a main table component and then composable multi-part components like header, body, footer and so on.

and then composable multi-part components like header, body, footer and so on. And so now let's go take a look at how the consumer experience for this component feels like. If I look for the table component in ui, we have an example table here. And so what we're going to do is copy the code for this and in the welcome.tsx page, I will overwrite all of this with the table component example. Alright, and here's the table

of this with the table component example. Alright, and here's the table and maybe let's add some padding around it. So p-8 and I'll move the closing div right at the end. And okay, that looks much better. So what we're gonna do now as we tend to is recreate a table component from scratch to understand why and how we arrive to this multi-component composable pattern.

Building Basic Table1:11

to understand why and how we arrive to this multi-component composable pattern that is present in this chatsy and UI component. Okay? So in my UI components I will add one more my component and this one will be my table that TSX. And here I'll paste some codes. This is just a basic static table so I can see it's a table with a caption, it has a table head with three columns and then the body has three rows inside of it. And so in welcome that TSX, we are going to

and then the body has three rows inside of it. And so in welcome.tsx, we are going to before the SHA UI table also have my table and let's actually add flex, flex-col and gap-20 to give ample spacing between the two tables. Alright, so they don't look exactly the same, but in essence these are both tables with a caption and we can work with that. And I'll quickly add a max-width container just for no good reason.

And I'll quickly add a maxWidth container just for no good reason. But here let's go max with four Excel and MX auto. It'll just add a maxWidth container so the table don't stretch too much. And the last thing I wanna do is just style the caption a tiny bit, class name padding two and then let's go textSM textSlate 600. Alright? And at this point I'm pretty happy with my basic table.

Adding Reusable Props2:24

Alright? And at this point I'm pretty happy with my basic table. Alright, so at this point, yes, uh, my table is a React component but it's just static markup. Everything is hardcoded so it's not reusable and it doesn't really bring any value. So let's make these components a little bit more reusable. So maybe the first thing we can abstract is the caption text here. So let's have a prop signature type MyTableProps

caption text here. So let's have a prop signature type MyTableProps and we'll have a caption which is a string. So we're going to receive the caption here and type it as MyTableProps. And inside the caption text we can go and replace this with the caption. Now we will of course lose the caption because we need to pass this caption to the MyTable component we use in the welcome page.

because we need to pass this caption to the my table component we use in the welcome page. And you can see tab script is telling us we have to, and so we can pass a caption of puppies currently living in my house because yes, they all do at the moment. Wish me luck. Alright, and here's the caption. Next we can probably abstract the column headers and also the data of course. So in our props, maybe let's add a columns,

and also the data of course. So in our props, maybe let's add a columns, which is an array of strings, but it shouldn't be optional because I think we always want to have table headings. And so basically here in the <thead> in that heading row, we are going to map over these columns, but I need to first receive it here as well. And so we're going to map over these columns that map, and then for each column we are going to return one <th> element.

and then for each column we are going to return one th element. So let's give you the key of column that'll do. And here instead of the name, we will also have the column. And so I should be able to delete these two hardcoded ths. Our table will be broken once again because now it's expecting a column property which is an array of strings. So let's go columns, an array which is name, age, and breed.

So let's go columns, an array which is name, age, and breed. And I've done something wrong here. Ooh, I have double m's, my eyes are getting old. All right, and our table still works. Nice. Okay. And next we can abstract the data inside the table. So we'll have an array of strings and we just have to make sure it matches the columns. That'll be the consumer's responsibility. So up here we will have data.

That'll be the consumer's responsibility. So up here we will have data. And this can be an array of an array of strings exactly like this. So each row is an array. And then inside the row we have an array of strings. Basically this time let's create the data first. So $cons, $puppies equals an array of an array of strings. So T-Rex, frisket and Bobby. And we will go past that data as the data prop.

So T-Rex, frisket and Bobby. And we will go past that data as the data prop and we will set that to puppies. And in the MyTable components we can receive that data. And just like we've done here for the table heading rows, we can go in the table body. And here we are going to go data.map. And for each row we basically are going to output one of these TR blocks. It needs a key for the key we can maybe have row.

of these TR blocks. It needs a key for the key we can maybe have row and then the first index of the row, maybe not the best, but that'll do. And then we need to have another iteration over the second level of array. So here row that map. And for each cell we are going to have one TD element. Again, the key will be the cell, which is a string. And here we can also pass the cell value.

Again, the key will be the cell, which is a string. And here we can also pass the cell value. I should be able to delete these two and then delete these two hardcoded trs. And hopefully you follow along and our table still works and now it's a little bit more flexible and reusable. So let's try to create another table using this interface that we've created in the welcome page. Under the my table example that we had. Let's have another one. My table,

Under the my table example that we had. Let's have another one. my table, should I trust copilot to come up with something? Huh? Interesting. So it's created four columns, invoice status, method and amount. And then uses the invoices data that is existing. So I think it tries to recreate that table data. So let's save that and nice work copilot. So you can see that it's used our new table interface or API and then it's created this table.

Handling Column Alignment6:45

So you can see that it's used our new table interface or API and then it's created this table. Uh, that looks different, but again it's a table and it seems to work. So this works all right? And it's all fun and games until you want to render one of the rows or the cells differently. Uh, let's take a look at this. For example, you can see that if I scroll down to the table from Shazi

For example, you can see that if I scroll down to the table from Shazi and ui, the last column is of right align the amount with the money here where here we have everything it looks like center or left aligned. So how would we go about right aligning a specific column? Okay, so if we look at the code we currently have for my table, we have this text left alignment hardcoded and we don't really have any way or specific props to specify the alignments differently.

and we don't really have any way or specific props to specify the alignments differently. And so I guess that would be a concern of the columns. But right now the columns are just an array of strings. My guess is we would need to invent some structure like the column now becomes an object that has a label, which is a string and an align property which yes can be left, center or right. And this is an array of objects with these two properties.

center or right. And this is an array of objects with these two properties. Hmm, okay, I guess that would work. But now we've got some refactoring to do. So uh, for the columns, now the column is an object with a label and an aligned property. So the key can be column do label. And here we can also pass column do label. And we've done all of that so that we can actually modify this class.

And we've done all of that so that we can actually modify this class. So I will remove the class here and use our now familiar cn function that we have in our utilities. And so I will combine and merge the P2 class with some conditional logic isColumn that align equals to right ? If so, uh, we want to have the text-right class,

that align equals to right question mark. If so, uh, we want to have the text-right class, otherwise we have two more possibilities. So let's go nuts and have a nested ternary operator. So we'll ask another question. Is column align equal to center question mark, then we want text-center and if not that means that it's left aligned so we want text-left. Alright, that might sound spooky

so we want text left. Alright, that might sound spooky but maybe I can be kind to you and sort of indent the code that way. And here's the second level nesting. So this is basically what's happening. I am not a super fan of this but this is exactly the whole reasoning behind this lesson. In the welcome page where we consume the tables, we need to do some refactoring.

In the welcome page where we consume the tables, we need to do some refactoring. The column array needs to be an object with label name and align left. And let's see if I can get rid of this and get copilots to fill the gaps for me. Of course I can. And can I yolo remove this and hope for the same? Of course I can. Alright, so copilot has given us some kitchen sink options here.

Of course I can. Alright, so copilot has given us some kitchen sink options here with left center, right? So let's go take a look at what it looks like and I gotta say it seems to be working left center right, left center right. Obviously this is just for the headings and we kinda want the rows underneath to kinda mimic that. And so we have to do more specific logic and uh, refactoring to our table.

And so we have to do more specific logic and uh, refactoring to our table. So let's keep those as is. And in my table we have some logic here for the headings, but we want to also have some logic for the table body rows. So we need to apply our stars in the td which is going to represent the column just like we have it here in the th and we are basically going to use the column headers alignment to align the rows. So we kinda have to rely on the heading

to use the column headers alignment to align the rows. So we kinda have to rely on the heading for the body cell alignment, which is sort of weird, but I think it's gonna work. So where I want to add my text left, right or center is inside this td. So once again let's use the CN function. P2 is going to be applied no matter what. Maybe we can rely on the cell index here. So index, so when we map over the row,

Maybe we can rely on the cell index here. So index, so when we map over the row, the first index will be zero, which is the column zero, which should look for the first index here. And then we can look at the align position. So if columns that index align is right, we're gonna do this. Yep, that looks like what I'm trying to do here. Basically we do the same logic that we've done before up here,

Basically we do the same logic that we've done before up here, but we are using the column index uh, to figure out how to align according to the definition of the columns. And yep, this is working great. I mean the implementation is not great but it works. So now what if some data cell needed to have something else than just a text string? Let's say that next to Bobby here we wanted to have a little bash that says guest

Rendering Custom Cell Content11:37

Let's say that next to Bobby here we wanted to have a little bash that says guest because Bobby is here as a guest temporarily. So how would we go about this? Well, inside our TD we just render the cell value and right now it's just a string. But technically if we had some JSX instead that would also work. So in the welcome page, this is gonna look a little bit crazy,

So in the welcome page, this is gonna look a little bit crazy, but in our puppies data, instead of just having Bobby, we could have some JSX markup. So I don't want it to look too crazy. So I'll create a little Guest component here, function guest that accepts a name which is a string and then it's going to return a flex container. Yes. And then here we're gonna have a P tag with the name and then here a little batch.

Yes. And then here we're gonna have a P tag with the name and then here a little badge. So bg-yellow-200 px-3 rounded-full text-sm text-yellow-800. And here, let's just say guest quick aside, you'd probably be way better off using a badge component from Shazi and UI for this here, but this is not what it's about. All right, let's save this. And then instead of Bobby here, I could totally use the Guest component here.

All right, let's save this. And then instead of Bobby here, I could totally use the guest component here with the name of Bobby. That seems really gross to have a JSX component in a data array. But if we look at our table, Bobby is now marked as a guest and it's working alright, alright, pump the brakes, let's take a step back and review a code because I feel like we creating this kinda weird ad hoc API to make things that we want happen happen.

because I feel like we creating this kinda weird ad hoc API to make things that we want happen happen and well that's exactly what we're doing. We have this weird component in the middle of a data array. And then if I look at the myTable components, uh, this is really not readable and super complex and one thing you might be tempted to do when that happens and you're like your brain cannot process what's going on is to compartmentalize and say okay, I want to have a TableHead component

to compartmentalize and say okay, I want to have a TableHead component and I can kind of abstract this and then the TableBody component. And so hopefully my brain can handle better the complexity if it's broken down. So let's try do this. Alright, so let's implement some sub components. I'll have function tableHead and we literally are going to copy

I'll have function tableHead and we literally are going to copy and paste the head portion. So now obviously you can see that we need to receive columns here, so let's accept those columns and the columns can be not hard coded but we can reuse our myTableProps and then reach for the columns key, which means that up here I can get rid of all of that and instead of that use tableHead and pass the columns.

that up here I can get rid of all of that and instead of that use table head and pass the columns and our tables still work. Let's do the same for the table body. I will cut that out and go here creates a function tableBody which returns all of that. Again, we are going to need uh, the data and we also need the columns because we are relying on them for the alignment styling.

and we also need the columns because we are relying on them for the alignment styling. So data columns and we can type this with data being myTableProps.data and columns being myTableProps.columns or if we want it to be fancy, we could replace all of this with peak and we would pick the data and columns properties from myTableProps. All right? And so I can come here below the table head and go tableBody and pass the data and the columns.

All right? And so I can come here below the table head and go table body and pass the data and the columns and our table is still working okay? And following that approach, our table head is alright I guess, but the table body is still pretty crazy. So why don't we break it down one more level and then have this table cell here. Extracted function, table cell return. Let me copy the whole TD block and that is what we return.

Extracted function, table cell return. Let me copy the whole TD block and that is what we return. And so it needs to know about the cell and once again the columns. So cell columns, columns is MyTable, props columns, but cell is not necessarily just a string because remember we have added this crazy components here. So basically it can be anything that can be rendered and reactNode is probably the answer. So here let's go reactNode.

and react node is probably the answer. So here let's go react node and now is when things get real funny. Uh, so you can see we were relying on the index of the cell to work out the positioning of the column, but now that we abstracting away the TD element the cell, we don't have the concept of which uh index it is at. So we can't use that. So what we should do instead is pass the contextual information that react needs to be able

So what we should do instead is pass the contextual information that React needs to be able to work at this alignment so we can instead of trying to rely on the index pass when we create a Cell component, when we use a Cell component, specify an align prop and then we can go left, center or right here. And so we don't need to rely on the index. Alright, let me show you what I mean up here because we are mapping over the rowData, we have access to the index when we replace this with a TableCell code,

because we are mapping over the row data, we have access to the index when we replace this with a table cell code, like so instead of passing the columns just for the sake of working out the alignment, I can create a new align prop and here pass the value of columns[index].align, which as you can see is going to be one of the three values. So let me delete all of that and hopefully that makes sense that at this point I can remove the columns dependency

and hopefully that makes sense that at this point I can remove the columns dependency because the columns was only used to work at the alignment. But we now have a specific align prop for this that we need to go update. So here is going to be align and align is going to replace these columns here and it's going to be my table props columns and we can sort of with but use a 0 here to access one of the indices.

and we can sort of with but use a zero here to access one of the indices of the columns array and then reach for the align property. I can now remove the column that index check because we just checking on the align property that's being passed and I can get rid of the key since we are not in the loop here and my god this is getting confusing and honestly at this point I'm hoping that you're getting overwhelmed and a bit confused

Refactoring with Children Composition18:09

and honestly at this point I'm hoping that you're getting overwhelmed and a bit confused because what we're doing is really weird and ad hoc and you can see how as more requirements come in the complexity is going to creep up and up and we headed for a pretty dark place. So now let's take a look at how composition in React can drastically simplify all these. And when I say composition, it's really using a specific prop in React

And when I say composition, it's really using a specific prop in React called the children prop. So we are going to try to refactor our components to use the children properly instead of passing all these different props that are needed to do a secret sauce and see how things really streamline and get way more easy to process. Okay, so let's start with the Leaf component, uh, of our Table, which is the Table.

Okay, so let's start with the leaf component, uh, of our table, which is the table cell right at the bottom here. This component is absurd as it is it receives a cell which is mostly a string but sometimes a react node, a React JSX component and then an aligned prop that we only use to output an align class. And really all of this, this aligned thing could be just a class name pass to it.

And really all of this, this aligned thing could be just a class name passed to it and well the cell could also be the children component. So check this out, we could write these components differently. I'll do it under function tableCellTwo and for this one instead I will receive the children which is React, that ReactNode and it doesn't need to know about the alignment. So as copilots suggests it's going to return this TD

and it doesn't need to know about the alignment. So as copilot suggests it's going to return this TD with the padding two and render the children. So let's try go here and comment that one out and see how we would go about this new component. TableCell two. And yes, the key is the cell. And remember all it cares about TableCell two is to have children. So instead of passing the cell as a prop we can just render the cell like this.

So instead of passing the cell as a prop we can just render the cell like this and it seems to work, obviously we don't have the alignments set correctly but let's do that now. And to me, instead of passing a value for a line which is left, center or right and then outputs text, right text center, text left there, we can just literally pass this as a class name, class name and in there I can move all of the logic that we are doing here.

class name and in there I can move all of the logic that we are doing here. So that feels like we're just moving the complexity somewhere else. Uh, and here we need to go back to columns bracket index that align. So like I was saying, it feels like we are shuffling the complexity left and right once again but stick with it. You'll see that there is a good reason and the outcome is gonna be much nicer.

You'll see that there is a good reason and the outcome is gonna be much nicer. And so now we passing the class name but it's not accepted as a prop in the table cell two. So let's go down here and also accept class name which can be an optional string which we should default to an empty string here. And you've seen this many times now in Chat CNUI, we are going to use CN to merge the component classes

And you've seen this many times now in chat CNUI, we are going to use CN to merge the component classes with the class name attribute pass to it and it's working. We have the left alignment, center alignment and right alignment. And if we compare these two components, uh, obviously tableCellTwo is a little bit simpler because we moved that logic here. Let's rename tableCellTwo to tableCell and kill tableCell.

Let's rename tableCellTwo to tableCell and kill tableCell. And here we are going to also rename that and remove that comment. So at this stage you still might not see the value of the refactoring we've just done and the children prop, but hopefully at the next level it's gonna start making more sense. Okay, so our TableCell component is just rendering its own children, but what if we took the same approach

Okay, so our table cell component is just rendering its own children, but what if we took the same approach for other parts like this table row here or the table body itself. So let me create one more component here, function, tableRow and it's going to accept children which is React, ReactNode. And I'm basically going to copy the TR element here. We don't need the key but it's going to return a TR and then inside of it the children value.

We don't need the key but it's going to return a TR and then inside of it the children value. We'll remove the key here and I'm going to do something crazy instead of rendering this whole mess here, I'm going to delete all of this and just render children. Yep. So I'm going to only accept children and so we are going to have everything broken here. Our tables have lost their table body but now we have this composable body row.

Our tables have lost their table body but now we have this composable Body row and Cell components that all accept and render children. And look at what I can do up here as an example in my Table components instead of having to worry about the data and passing down the columns. The TableBody component can just be a wrapping component. You can see that it's going to ask for children because we've typed it that way. Of course I could do it dynamically like the

because we've typed it that way. Of course I could do it dynamically like the suggestion from copilot is here, which is the equivalent of what we had. But take a look at how simple it feels to have a table row and inside of it have a table cell that says hello, how are you? So that's going to hard code both of our tables with one row and the same table values. But you can see how if we were to expose these components

and the same table values. But you can see how if we were to expose these components to the consumer, they could assemble very easily a table like this with a lot more flexibility and it's also much simpler to implement for the developer. So here's a concrete example. You can see how we handled in the table cell the class name attribute and we were checking for the column alignment and doing this ternary nesting, all of that to pass one of text-center, text-left, or text-right classes.

and doing this turnery nesting, all of that to pass one of text-center, text-left, or text-right classes. Well look at this in this case I can do class name text-right and that's going to write the line my table cell and on the middle here class name text-center and that works. And if I wanted the how here to be a different nodes than just a string, uh, instead of passing it in the data array I could have here for example an anchor link instead of just a normal text.

of passing it in the data array I could have here for example an anchor link instead of just a normal text. And let's give you the class name of underline and this is now outputting a link and of course you could have an image or anything you want in the cells. So instead of inventing and maintaining custom API and props and having to pass them multiple components deep, we can let the consumer compose elements together to achieve any results that they want.

we can let the consumer compose elements together to achieve any results that they want. Obviously you don't want to do all this customization inside the MyTable component, but what you want to do is export the multiple parts and let the consumer consume all of them. So before we can do that, we need to transform the TableHead component and the actual MyTable component to also render their children and be composable.

and the actual my table component to also render their children and be composable. So I'll go down to the table head and hopefully here you can see that this could be a tableRow component and this could be a new tableHeading component. And again here all the complexity is about aligning the column properly. I can get rid of all of that and just render the children. And so we just accept the children as ReactNodes.

I can get rid of all of that and just render the children. And so we just accept the children as react nodes. And now even our MyTable component, I might keep the caption in here, although this could be a TableCaption component and I'm going to accept children and instead of all of this here, the body and the head, I'm going to render children. And so we are offloading the responsibility but also the capability to assemble tables.

And so we offloading the responsibility but also the capability uh, to assemble tables as you see fit to the consumer. So now for this to work I need to export the tableHead, the tableBody, the tableRow and the tableCell. And I've just realized that all of these components will clash uh, their namespace with the actual UI.

of these components will clash uh, their namespace with the actual UI. So we could E them when we import them, but we can also follow the convention of my components. So my tableHead, my tableBody, my tableRow, my tableCell and a quick aside I want to hear, but you would really want here for all of the components to do the className merging treatment like we've done for there. So you can actually customize every

we've done for there. So you can actually customize every of these components here. They don't have any styles but in a real-world scenario for it to make sense, each component would have specific classes applied like a default coherent style and then it's override able by using the class name attribute which is going to be merged with cn.

by using the class name attribute which is going to be merged with cn. Alright, so let's take a look. We have uh, my table component that currently renders a table with a caption that goes to the bottom and then whatever we pass to it with composition. And so all of these props interface that we had invented with uh, these custom ways to handle the logic that we need, we really don't need any of that.

with uh, these custom ways to handle the logic that we need, we really don't need any of that because our props API is now much less complicated. And you know what? Real quick, why not? Let's also make an export for a function. myTableCaption which accepts children, it's going to render a caption element with the children, which means I can get rid of that here and not accept it here. And we fully bought into our components

Composing Table in Consumer27:40

and not accept it here. And we fully bought into our components that render the children and are composable. And so now let's go see how the consumer experience feels to build these tables. So what we have here is obviously not working because we need to not pass all this bespoke data but compose rather. So I will comment these two tables out and we'll go here and create our own.

So I will comment these two tables out and we'll go here and create our own. So my table, which is a wrapping component inside of it, it's going to have my table caption that we need to import. Uh, why is it not working? Uh oh I am not returning the JSX. And so that is why now we are returning that JSX. So that should work. Okay? So it works and we have a table that just has a caption but now I can go my table head component.

So it works and we have a table that just has a caption but now I can go my TableHead component and inside of it my TableRow and see that's interesting. I'll accept this here but it's not correct technically because the table cell outputs a td element but in the table head we want a th for each cell. So we could create a TableHeading component. I will duplicate this component and export a my TableHeading which is going to return a th instead.

and export a myTableHeading which is going to return a TH instead. And here we will turn all of these myTableCells to myTableHeading. Alright? And so after the tableHead we can have myTableBody. Let's also import this and you see that we've hardcoded things here but we can still use dynamic data. Uh, remember we have the puppies here.

but we can still use dynamic data. Uh, remember we have the puppies here and so we can go yes, puppies that map and then have a table row for each row. Uh, we don't want the font-medium here, but what we can do is on the third cell, the last cell go class name text-right to right-align it and let's go fix the th elements. They centered by default, we want them left-align.

and let's go fix the th elements. They are centered by default, we want them left aligned by default and then we need to write a line. The third one here. So here the third one here, class name equals text-right? And in the table heading here, which is the th element also wants to have a default of text-left. And if text-right is passed, like we've just done, it's going to override it and delete this one because of tewin merge.

it's going to override it and delete this one because of tewin merge. And there you go. Uh, I'm gonna get rid of the width a hundred pixel. That's a copilot decided to add, but it's a good example that you can completely customize how you want things to work. And here's our table that is now assembled with component composition and is much more flexible but also much easier to maintain.

with component composition and is much more flexible but also much easier to maintain. And the multiple parts for this, my Table components are also all super simple and basically just rendering their own children and being composable. And then with this pattern of merging the classes to make it customizable. So of course this is a contrived and simple example, but let's take a look at the Shaea and UITable components.

Comparing to Shadcn Implementation30:43

So of course this is a contrived and simple example, but let's take a look at the shaea and UI table components. The source code that was generated when we added this component and compare to what we've got this time, I will go in the table components, the one from UI. And so we have a table component that gets classes and then all the other props including the children prop inside of it and then has a table element that spreads all the props. So we merging the stars

that spreads all the props. So we merging the stars and then we rendering all the props including the children. When you see a self-closing tag with spread props typically keep in mind that the children are being part of this. And so then we have the table header, same deal classes to be merged and rest props table, body table footer. This is the exact same thing that we've done. The difference is that it has a data slot for each element.

This is the exact same thing that we've done. The difference is that it has a data slot for each element to allow certain specific style approach. It has merged the class names for every component. And the main difference I guess is that the children are not the props received, but the whole component props for the tag that it needs and then all of it is spread if we keep going. The same happens for all the other components. And then all of these are being exported in

The same happens for all the other components. And then all of these are being exported in an object at the end. Just to make it crystal clear here, if I was to update my tableCell component, I could get rid of children and also the default value of className, I could have componentProps, TD and done here because I'm not structuring the children, I would instead spread all the props and self close the element.

I would instead spread all the props and self close the element and oops, I need of course to receive them here. So this is a more complete approach because it's gonna allow you to pass any HTML attributes that the td element accepts to it. And because the props are spread, it's going to be added accordingly. Alright, I know that this episode was confusing in some parts.

Alright, I know that this episode was confusing in some parts and it's somewhat intentional because I really wanted you to understand the value of composition in React and the children prop. Uh, instead of having a set of props that we keep adding and adding to as the specific use cases arrive and it never ends well. So hopefully you can see the value of this passing the children and rendering them.

So hopefully you can see the value of this passing the children and rendering them and then composing components together. And now when you see a Chat UI component that has all these multiple parts, you know why it's like that? Alright, I'll see you next one.

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