-
Notifications
You must be signed in to change notification settings - Fork 6
Composite Components Demo
The great thing about making small, unopinionated, reusable components is that you can start to combine them together to make larger, more capable, slightly opinionated, reusable components.
Why don't we just stick with primitive controls and never worry about combining them together? Anything we create here could be created by the application dev using primitives, just like we are. Here are a few reasons:
- Many controls that you might consider primitives are actually composite controls. A dropdown is a button that opens up a list of buttons.
- Many different developers will implement a composite control in many different ways. Having a consistent UI from page to page, from app to app, is incredibly important.
- Good composite controls are full of best practices, great accessibility, and optimized performance.
- Sometimes you need an "opinion" about how a given UI should look or work. That's part of the design system.
After starting with our obligatory $ npm run scaffold TodoItem
we can start pulling in the components that we are going to be combining. This is a pretty simple composite, but now that we've established it, we can continue to improve it over time as we add more functionality.
import Button from "../Button/Button.vue";
import Textfield from "../Textfield/Textfield.vue";
export default {
components: {Button, Textfield},
props: ['todo']
};
You'll notice we have a single prop being passed into this component, a singular todo
. As I said above, this component is a bit more opinionated than our textfield, or button. We know "what" this component will be representing, and we know some of the basic actions we can take on a todo, but we will avoid any actual business logic so that this component could be used in various applications that need todo items.
Here are things we know a Todo should be able to do.
- Render a todo item that includes a todo title
- Modify the title contained in that todo
- Emit that editing is finished (passing the updated todo)
- Signal that it would like to be deleted
Note that none of these callbacks/emits actually DO anything. They just tell the parent that they want something to happen, and the parent can decide how to achieve that goal.
So here's how the markup will look.
<div class="TodoItem">
<Textfield
variant="edit"
:value="todo.title"
@input="(value) => { todo.title = value }"
@blur="$emit(`doneEdit`, todo)"
@enter="$emit(`doneEdit`, todo)"
/>
<Button class="delete" @click="$emit(`removeTodo`, todo)">×</Button>
</div>
Let's quickly walk through each line.
- We have two different Textfield variants. This is the one called "edit".
- Set the value property of our Textfield to the value found in the
todo.title
. Notice the:
before the property that is Vue's shorthand forv-bind
. - On new input, we'll change the value of the
todo.title
to whatever the new value is - On both blur and enter we'll emit
doneEdit
that "editing is completed", and we'll pass the new todo - Our delete button will emit that we'd like to
removeTodo
and includes a copy of the todo to be removed
Since we're using primitive controls to build up this composite, we aren't going to need many styles. As a rule, I try to avoid writing styles in the parent that change anything other than layout or visibility.
.TodoItem {
display: flex;
.delete {
display: none;
}
&:hover {
.delete {
display: block;
}
}
}
No need for floats here! Just one display flex
and some hide/show on hover for the button, and we're done.