Skip to content

Commit 0bd2af7

Browse files
authored
docs (#26): migrate cookbook (#413)
* fix: border color overridden by general styles * docs (#26): add cookbook home page * docs (#26): add editable svg cookbook example * feature (#26): add cookbook to top nav
1 parent 7e0831c commit 0bd2af7

File tree

4 files changed

+267
-7
lines changed

4 files changed

+267
-7
lines changed

src/.vuepress/config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
const sidebar = {
2+
cookbook: [
3+
{
4+
title: 'Cookbook',
5+
collapsable: false,
6+
children: ['/cookbook/', '/cookbook/editable-svg-icons']
7+
}
8+
],
29
guide: [
310
{
411
title: 'Essentials',
@@ -231,6 +238,7 @@ module.exports = {
231238
items: [
232239
{ text: 'Guide', link: '/guide/introduction' },
233240
{ text: 'Style Guide', link: '/style-guide/' },
241+
{ text: 'Cookbook', link: '/cookbook/' },
234242
{ text: 'Examples', link: '/examples/markdown' }
235243
]
236244
},
@@ -293,6 +301,7 @@ module.exports = {
293301
collapsable: false,
294302
'/guide/': sidebar.guide,
295303
'/community/': sidebar.guide,
304+
'/cookbook/': sidebar.cookbook,
296305
'/api/': sidebar.api,
297306
'/examples/': sidebar.examples
298307
},

src/.vuepress/theme/global-components/Badge.vue

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ export default {
1212
default: 'top'
1313
}
1414
},
15-
render (h, { props, slots }) {
16-
return h('span', {
17-
class: ['badge', props.type],
18-
style: {
19-
verticalAlign: props.vertical
20-
}
21-
}, props.text || slots().default)
15+
render(h, { props, slots }) {
16+
return h(
17+
'span',
18+
{
19+
class: ['badge', props.type],
20+
style: {
21+
verticalAlign: props.vertical
22+
}
23+
},
24+
props.text || slots().default
25+
)
2226
}
2327
}
2428
</script>
@@ -35,10 +39,13 @@ export default {
3539
background-color #42b983
3640
&.tip, &.green
3741
background-color $badgeTipColor
42+
border-color $badgeTipColor
3843
&.error
3944
background-color $badgeErrorColor
45+
border-color $badgeErrorColor
4046
&.warning, &.warn, &.yellow
4147
background-color $badgeWarningColor
48+
border-color $badgeWarningColor
4249
& + &
4350
margin-left 5px
4451
</style>

src/cookbook/editable-svg-icons.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Editable SVG Icon Systems
2+
3+
## Base Example
4+
5+
There are many ways to create an SVG Icon System, but one method that takes advantage of Vue's capabilities is to create editable inline icons as components. Some of the advantages of this way of working is:
6+
7+
- They are easy to edit on the fly
8+
- They are animatable
9+
- You can use standard props and defaults to keep them to a typical size or alter them if you need to
10+
- They are inline, so no HTTP requests are necessary
11+
- They can be made accessible dynamically
12+
13+
First, we'll create a folder for all of the icons, and name them in a standardized fashion for easy retrieval:
14+
15+
- `components/icons/IconBox.vue`
16+
- `components/icons/IconCalendar.vue`
17+
- `components/icons/IconEnvelope.vue`
18+
19+
Here's an example repo to get you going, where you can see the entire setup: [https://github.com/sdras/vue-sample-svg-icons/](https://github.com/sdras/vue-sample-svg-icons/)
20+
21+
![Documentation site](https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/screendocs.jpg 'Docs demo')
22+
23+
We'll create a base icon (`IconBase.vue`) component that uses a slot.
24+
25+
```html
26+
<template>
27+
<svg
28+
xmlns="http://www.w3.org/2000/svg"
29+
:width="width"
30+
:height="height"
31+
viewBox="0 0 18 18"
32+
:aria-labelledby="iconName"
33+
role="presentation"
34+
>
35+
<title :id="iconName" lang="en">{{ iconName }} icon</title>
36+
<g :fill="iconColor">
37+
<slot />
38+
</g>
39+
</svg>
40+
</template>
41+
```
42+
43+
You can use this base icon as is- the only thing you might need to update is the `viewBox` depending on the `viewBox` of your icons. In the base, we're making the `width`, `height`, `iconColor`, and name of the icon props so that it can be dynamically updated with props. The name will be used for both the `<title>` content and its `id` for accessibility.
44+
45+
Our script will look like this, we'll have some defaults so that our icon will be rendered consistently unless we state otherwise:
46+
47+
```js
48+
export default {
49+
props: {
50+
iconName: {
51+
type: String,
52+
default: 'box'
53+
},
54+
width: {
55+
type: [Number, String],
56+
default: 18
57+
},
58+
height: {
59+
type: [Number, String],
60+
default: 18
61+
},
62+
iconColor: {
63+
type: String,
64+
default: 'currentColor'
65+
}
66+
}
67+
}
68+
```
69+
70+
The `currentColor` property that's the default on the fill will make the icon inherit the color of whatever text surrounds it. We could also pass in a different color as a prop if we wish.
71+
72+
We can use it like so, with the only contents of `IconWrite.vue` containing the paths inside the icon:
73+
74+
```html
75+
<icon-base icon-name="write"><icon-write /></icon-base>
76+
```
77+
78+
Now, if we'd like to make many sizes for the icon, we can do so very easily:
79+
80+
```html
81+
<p>
82+
<!-- you can pass in a smaller `width` and `height` as props -->
83+
<icon-base width="12" height="12" icon-name="write"><icon-write /></icon-base>
84+
<!-- or you can use the default, which is 18 -->
85+
<icon-base icon-name="write"><icon-write /></icon-base>
86+
<!-- or make it a little bigger too :) -->
87+
<icon-base width="30" height="30" icon-name="write"><icon-write /></icon-base>
88+
</p>
89+
```
90+
91+
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/Screen%20Shot%202018-01-01%20at%204.51.40%20PM.png" width="450" />
92+
93+
## Animatable Icons
94+
95+
Keeping icons in components comes in very handy when you'd like to animate them, especially on an interaction. Inline SVGs have the highest support for interaction of any method. Here's a very basic example of an icon that's animated on click:
96+
97+
```html
98+
<template>
99+
<svg
100+
@click="startScissors"
101+
xmlns="http://www.w3.org/2000/svg"
102+
viewBox="0 0 100 100"
103+
width="100"
104+
height="100"
105+
aria-labelledby="scissors"
106+
role="presentation"
107+
>
108+
<title id="scissors" lang="en">Scissors Animated Icon</title>
109+
<path id="bk" fill="#fff" d="M0 0h100v100H0z" />
110+
<g ref="leftscissor">
111+
<path d="M..." />
112+
...
113+
</g>
114+
<g ref="rightscissor">
115+
<path d="M..." />
116+
...
117+
</g>
118+
</svg>
119+
</template>
120+
```
121+
122+
```js
123+
import { TweenMax, Sine } from 'gsap'
124+
125+
export default {
126+
methods: {
127+
startScissors() {
128+
this.scissorAnim(this.$refs.rightscissor, 30)
129+
this.scissorAnim(this.$refs.leftscissor, -30)
130+
},
131+
scissorAnim(el, rot) {
132+
TweenMax.to(el, 0.25, {
133+
rotation: rot,
134+
repeat: 3,
135+
yoyo: true,
136+
svgOrigin: '50 45',
137+
ease: Sine.easeInOut
138+
})
139+
}
140+
}
141+
}
142+
```
143+
144+
We're applying `refs` to the groups of paths we need to move, and as both sides of the scissors have to move in tandem, we'll create a function we can reuse where we'll pass in the `refs`. The use of GreenSock helps resolve animation support and `transform-origin` issues across browser.
145+
146+
<p data-height="300" data-theme-id="0" data-slug-hash="dJRpgY" data-default-tab="result" data-user="Vue" data-embed-version="2" data-pen-title="Editable SVG Icon System: Animated icon" class="codepen">See the Pen <a href="https://codepen.io/team/Vue/pen/dJRpgY/">Editable SVG Icon System: Animated icon</a> by Vue (<a href="https://codepen.io/Vue">@Vue</a>) on <a href="https://codepen.io">CodePen</a>.</p><script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
147+
148+
<p style="margin-top:-30px">Pretty easily accomplished! And easy to update on the fly.</p>
149+
150+
You can see more animated examples in the repo [here](https://github.com/sdras/vue-sample-svg-icons/)
151+
152+
## Additional Notes
153+
154+
Designers may change their minds. Product requirements change. Keeping the logic for the entire icon system in one base component means you can quickly update all of your icons and have it propagate through the whole system. Even with the use of an icon loader, some situations require you to recreate or edit every SVG to make global changes. This method can save you that time and pain.
155+
156+
## When To Avoid This Pattern
157+
158+
This type of SVG icon system is really useful when you have a number of icons that are used in different ways throughout your site. If you're repeating the same icon many times on one page (e.g. a giant table with a delete icon in each row), it might make more sense to have all of the sprites compiled into a sprite sheet and use `<use>` tags to load them.
159+
160+
## Alternative Patterns
161+
162+
Other tooling to help manage SVG icons includes:
163+
164+
- [svg-sprite-loader](https://github.com/kisenka/svg-sprite-loader)
165+
- [svgo-loader](https://github.com/rpominov/svgo-loader)
166+
167+
These tools bundle SVGs at compile time, but make them a little harder to edit during runtime, because `<use>` tags can have strange cross-browser issues when doing anything more complex. They also leave you with two nested `viewBox` properties and thus two coordinate systems. This makes the implementation a little more complex.

src/cookbook/index.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Introduction
2+
3+
## The Cookbook vs the Guide
4+
5+
How is the cookbook different from the guide? Why is this necessary?
6+
7+
- **Greater Focus**: In the guide, we're essentially telling a story. Each section builds on and assumes knowledge from each previous section. In the cookbook, each recipe can and should stand on its own. This means recipes can focus on one specific aspect of Vue, rather than having to give a general overview.
8+
9+
- **Greater Depth**: To avoid making the guide too long, we try to include only the simplest possible examples to help you understand each feature. Then we move on. In the cookbook, we can include more complex examples, combining features in interesting ways. Each recipe can also be as long and detailed as it needs to be, in order to fully explore its niche.
10+
11+
- **Teaching JavaScript**: In the guide, we assume at least intermediate familiarity with ES5 JavaScript. For example, we won't explain how `Array.prototype.filter` works in a computed property that filters a list. In the cookbook however, essential JavaScript features (including ES6/2015+) can be explored and explained in the context of how they help us build better Vue applications.
12+
13+
- **Exploring the Ecosystem**: For advanced features, we assume some ecosystem knowledge. For example, if you want to use single-file components in Webpack, we don't explain how to configure the non-Vue parts of the Webpack config. In the cookbook, we have the space to explore these ecosystem libraries in more depth - at least to the extent that is universally useful for Vue developers.
14+
15+
::: tip
16+
With all these differences, please note that the cookbook is still _not_ a step-by-step manual. For most of its content, you are expected to have a basic understanding of concepts like HTML, CSS, JavaScript, npm/yarn, etc.
17+
:::
18+
19+
## Cookbook Contributions
20+
21+
### What we're looking for
22+
23+
The Cookbook gives developers examples to work off of that both cover common or interesting use cases, and also progressively explain more complex detail. Our goal is to move beyond a simple introductory example, and demonstrate concepts that are more widely applicable, as well as some caveats to the approach.
24+
25+
If you're interested in contributing, please initiate collaboration by filing an issue under the tag **cookbook idea** with your concept so that we can help guide you to a successful pull request. After your idea has been approved, please follow the template below as much as possible. Some sections are required, and some are optional. Following the numerical order is strongly suggested, but not required.
26+
27+
Recipes should generally:
28+
29+
- Solve a specific, common problem
30+
- Start with the simplest possible example
31+
- Introduce complexities one at a time
32+
- Link to other docs, rather than re-explaining concepts
33+
- Describe the problem, rather than assuming familiarity
34+
- Explain the process, rather than just the end result
35+
- Explain the pros and cons of your strategy, including when it is and isn't appropriate
36+
- Mention alternative solutions, if relevant, but leave in-depth explorations to a separate recipe
37+
38+
We request that you follow the template below. We understand, however, that there are times when you may necessarily need to deviate for clarity or flow. Either way, all recipes should at some point discuss the nuance of the choice made using this pattern, preferably in the form of the alternative patterns section.
39+
40+
### Base Example <Badge text="required" type="error" />
41+
42+
1. Articulate the problem in a sentence or two.
43+
2. Explain the simplest possible solution in a sentence or two.
44+
3. Show a small code sample.
45+
4. Explain what this accomplishes in a sentence.
46+
47+
### Details about the Value <Badge text="required" type="error" />
48+
49+
1. Address common questions that one might have while looking at the example. (Blockquotes are great for this)
50+
2. Show examples of common missteps and how they can be avoided.
51+
3. Show very simple code samples of good and bad patterns.
52+
4. Discuss why this may be a compelling pattern. Links for reference are not required but encouraged.
53+
54+
### Real-World Example <Badge text="required" type="error" />
55+
56+
Demonstrate the code that would power a common or interesting use case, either by:
57+
58+
1. Walking through a few terse examples of setup, or
59+
2. Embedding a codepen/jsfiddle example
60+
61+
If you choose to do the latter, you should still talk through what it is and does.
62+
63+
### Additional Context <Badge text="optional" />
64+
65+
It's extremely helpful to write a bit about this pattern, where else it would apply, why it works well, and run through a bit of code as you do so or give people further reading materials here.
66+
67+
### When To Avoid This Pattern <Badge text="optional" />
68+
69+
This section is not required, but heavily recommended. It won't make sense to write it for something very simple such as toggling classes based on state change, but for more advanced patterns like mixins it's vital. The answer to most questions about development is ["It depends!"](https://codepen.io/rachsmith/pen/YweZbG), this section embraces that. Here, we'll take an honest look at when the pattern is useful and when it should be avoided, or when something else makes more sense.
70+
71+
### Alternative Patterns <Badge text="required with avoidance section" type="warning" />
72+
73+
This section is required when you've provided the section above about avoidance. It's important to explore other methods so that people told that something is an antipattern in certain situations are not left wondering. In doing so, consider that the web is a big tent and that many people have different codebase structures and are solving different goals. Is the app large or small? Are they integrating Vue into an existing project, or are they building from scratch? Are their users only trying to achieve one goal or many? Is there a lot of asynchronous data? All of these concerns will impact alternative implementations. A good cookbook recipe gives developers this context.
74+
75+
## Thank you
76+
77+
It takes time to contribute to documentation, and if you spend the time to submit a PR to this section of our docs, you do so with our gratitude.

0 commit comments

Comments
 (0)