Skip to content

Commit d2bef17

Browse files
authored
feat(icons): optional icon components (#4489)
1 parent 6bd8100 commit d2bef17

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+17364
-119
lines changed

docs/components/componentdoc.vue

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<section v-if="component" class="bd-content">
33
<b-row tag="header" align-v="center">
44
<b-col sm="9">
5-
<anchored-heading :id="`comp-ref-${componentName}`" level="3">
5+
<anchored-heading :id="`comp-ref-${componentNameClean}`" level="3">
66
<code class="notranslate bigger" translate="no">{{ tag }}</code>
77
</anchored-heading>
88
<b-badge v-if="version" variant="success">v{{ version }}+</b-badge>
@@ -16,7 +16,13 @@
1616
</b-badge>
1717
</b-col>
1818
<b-col sm="3" class="text-sm-right">
19-
<b-btn variant="outline-secondary" size="sm" :href="githubURL" target="_blank">
19+
<b-btn
20+
v-if="githubURL"
21+
variant="outline-secondary"
22+
size="sm"
23+
:href="githubURL"
24+
target="_blank"
25+
>
2026
View source
2127
</b-btn>
2228
</b-col>
@@ -311,18 +317,23 @@ ul.component-ref-mini-toc:empty {
311317

312318
<script>
313319
import Vue from 'vue'
314-
import kebabCase from 'lodash/kebabCase'
315-
import AnchoredHeading from './anchored-heading'
316320
// Fallback descriptions for common props (mainly router-link props)
317321
import commonProps from '../common-props.json'
322+
import { kebabCase } from '../utils'
323+
import AnchoredHeading from './anchored-heading'
318324
319325
export default {
320326
name: 'BDVComponentdoc',
321327
components: { AnchoredHeading },
322328
props: {
323329
component: {},
330+
srcComponent: {
331+
// This prop is used only when the above `component` is a
332+
// "fake" component. This prop specifies a "real" component
333+
// to use when grabbing the component definition options
334+
},
324335
propsMeta: {
325-
// For getting pro descriptions
336+
// For getting prop descriptions
326337
type: Array,
327338
default: () => []
328339
},
@@ -349,7 +360,7 @@ export default {
349360
},
350361
computed: {
351362
componentOptions() {
352-
const component = Vue.options.components[this.component]
363+
const component = Vue.options.components[this.srcComponent || this.component]
353364
if (!component) {
354365
return {}
355366
}
@@ -492,15 +503,22 @@ export default {
492503
return this.slots ? this.slots.map(s => ({ ...s })) : []
493504
},
494505
componentName() {
495-
return kebabCase(this.component)
506+
return kebabCase(this.component).replace('{', '-{')
507+
},
508+
componentNameClean() {
509+
return this.componentName.replace('{', '').replace('}', '')
496510
},
497511
tag() {
498512
return `<${this.componentName}>`
499513
},
500514
githubURL() {
515+
const name = this.componentName.replace(/^b-/, '')
516+
if (name.indexOf('{') !== -1) {
517+
// Example component (most likely an auto generated component)
518+
return ''
519+
}
501520
const base = 'https://github.com/bootstrap-vue/bootstrap-vue/tree/dev/src/components'
502521
const slug = this.$route.params.slug
503-
const name = kebabCase(this.component).replace(/^b-/, '')
504522
// Always point to the .js file (which may import a .vue file)
505523
return `${base}/${slug}/${name}.js`
506524
}

docs/components/feedback.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export default {
99
show() {
1010
const name = this.$route.name
1111
const slug = this.$route.params.slug
12-
return slug || name === 'docs'
12+
return slug || name === 'docs' || name === 'docs-icons'
1313
},
1414
reportIssueUrl() {
1515
// Add appreciate query params for proper issue title
@@ -18,22 +18,24 @@ export default {
1818
editPageUrl() {
1919
const name = this.$route.name
2020
const slug = this.$route.params.slug
21-
let path = '/'
21+
let path = ''
2222
if (name === 'docs') {
23-
path = `/docs/markdown/intro/README.md`
23+
path = `docs/markdown/intro/README.md`
2424
} else if (name === 'docs-components-slug') {
25-
path = `/src/components/${slug}/README.md`
25+
path = `src/components/${slug}/README.md`
26+
} else if (name === 'docs-icons') {
27+
path = `src/icons/README.md`
2628
} else if (name === 'docs-directives-slug') {
27-
path = `/src/directives/${slug}/README.md`
29+
path = `src/directives/${slug}/README.md`
2830
} else if (name === 'docs-reference-slug') {
29-
path = `/docs/markdown/reference/${slug}/README.md`
31+
path = `docs/markdown/reference/${slug}/README.md`
3032
} else if (name === 'docs-misc-slug') {
3133
if (slug === 'changelog') {
32-
path = '/CHANGELOG.md'
34+
path = 'CHANGELOG.md'
3335
} else if (slug === 'contributing') {
34-
path = '/CONTRIBUTING.md'
36+
path = 'CONTRIBUTING.md'
3537
} else if (slug === 'settings') {
36-
path = '/docs/markdown/misc/settings/README.md'
38+
path = 'docs/markdown/misc/settings/README.md'
3739
}
3840
}
3941
return `${this.baseUrl}/tree/dev/${path}`

docs/components/footer.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<li><b-link to="/docs" exact>Getting started</b-link></li>
2020
<li><b-link to="/docs/components" exact>Components</b-link></li>
2121
<li><b-link to="/docs/directives" exact>Directives</b-link></li>
22+
<li><b-link to="/docs/icons" exact>Icons</b-link></li>
2223
<li><b-link to="/docs/reference" exact>Reference</b-link></li>
2324
<li><b-link to="/docs/misc" exact>Miscellaneous</b-link></li>
2425
<li><b-link to="/play" exact>Playground</b-link></li>

docs/components/header.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<b-nav-item to="/docs" active-class="active" exact>Docs</b-nav-item>
3434
<b-nav-item to="/docs/components" active-class="active">Components</b-nav-item>
3535
<b-nav-item to="/docs/directives" active-class="active">Directives</b-nav-item>
36+
<b-nav-item to="/docs/icons" active-class="active">Icons</b-nav-item>
3637
<b-nav-item to="/docs/reference" active-class="active">Reference</b-nav-item>
3738
<b-nav-item to="/docs/misc" active-class="active">Misc</b-nav-item>
3839
<b-nav-item to="/play" active-class="active">Play</b-nav-item>

docs/components/icons-table.vue

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<template>
2+
<div
3+
key="_bv-icons-table_"
4+
class="bv-icons-table notranslate"
5+
role="group"
6+
aria-labeledby="bv-icons-table-title"
7+
>
8+
<b-row align-v="start">
9+
<b-col md="5" lg="6">
10+
<div id="bv-icons-table-title" class="h3 text-muted mb-3 mb-md-0">
11+
Icon explorer
12+
</div>
13+
</b-col>
14+
<b-col md="7" lg="6">
15+
<b-form @submit.prevent>
16+
<b-form-group
17+
label="Search icons"
18+
label-for="bv-icons-table-search"
19+
label-cols-sm="auto"
20+
label-align-sm="right"
21+
:description="`Showing ${filteredIcons.length} of ${totalIcons} icons`"
22+
>
23+
<b-input-group>
24+
<b-input-group-prepend is-text>
25+
<b-icon icon="search"></b-icon>
26+
</b-input-group-prepend>
27+
<b-form-input
28+
id="bv-icons-table-search"
29+
key="_bv-icons-table-search_"
30+
v-model="iconFilter"
31+
type="search"
32+
debounce="250"
33+
aria-controls="bv-icons-table-result"
34+
></b-form-input>
35+
</b-input-group>
36+
</b-form-group>
37+
</b-form>
38+
</b-col>
39+
</b-row>
40+
<div id="bv-icons-table-result">
41+
<transition-group
42+
tag="ul"
43+
name="flip-icon-list"
44+
class="row row-cols-3 row-cols-sm-4 row-cols-lg-6 list-unstyled mb-n3 position-relative"
45+
>
46+
<b-col
47+
v-for="icon in filteredIcons"
48+
:key="`_icon_${icon.name}`"
49+
tag="li"
50+
class="flip-icon-list-icon d-inline-flex flex-column mb-3 text-center"
51+
>
52+
<div class="card bg-light p-3" :title="icon.name">
53+
<b-icon :icon="icon.name" class="mx-auto"></b-icon>
54+
</div>
55+
<b-form-text class="mt-1 text-break" :title="icon.name">{{ icon.name }}</b-form-text>
56+
</b-col>
57+
</transition-group>
58+
<div aria-live="polite" aria-atomic="true">
59+
<b-alert
60+
:show="filteredIcons.length === 0"
61+
:role="null"
62+
:aria-live="null"
63+
:aria-atomic="null"
64+
fade
65+
variant="light"
66+
class="text-center mt-4 d-flex align-items-center justify-content-center"
67+
>
68+
<b-icon icon="alert-triangle-fill" aria-hidden="true"></b-icon>
69+
<span>No matching icons found. Try searching again.</span>
70+
</b-alert>
71+
</div>
72+
</div>
73+
</div>
74+
</template>
75+
76+
<style lang="scss" scoped>
77+
.bv-icons-table {
78+
position: relative;
79+
}
80+
81+
#bv-icons-table-result /deep/ .bi {
82+
font-size: 2rem;
83+
}
84+
85+
.form-group /deep/ .form-text {
86+
text-align: right;
87+
}
88+
89+
// Icon zoom on hover
90+
.flip-icon-list-icon /deep/ .card {
91+
.bi {
92+
transition: transform 0.15s;
93+
}
94+
95+
&:hover .bi {
96+
transform: scale(2);
97+
}
98+
}
99+
100+
// Transion group classes
101+
.flip-icon-list-icon {
102+
transition: all 0.15s;
103+
}
104+
105+
.flip-icon-list-move {
106+
transition: transform 0.3s;
107+
transition-delay: 0.15s;
108+
}
109+
110+
.flip-icon-list-enter,
111+
.flip-icon-list-leave-to {
112+
opacity: 0;
113+
transform: scale(0.75);
114+
}
115+
116+
.flip-icon-list-enter-active {
117+
transition-delay: 0.3s;
118+
}
119+
120+
.flip-icon-list-leave-active {
121+
position: absolute;
122+
}
123+
</style>
124+
125+
<script>
126+
import { iconNames } from '~/../src/icons'
127+
128+
const icons = iconNames
129+
.filter(name => name !== 'BIcon')
130+
.sort()
131+
.map(fullName => {
132+
return {
133+
component: fullName,
134+
name: fullName
135+
.replace(/^BIcon/, '')
136+
.replace(/\B([A-Z])/g, '-$1')
137+
.toLowerCase()
138+
}
139+
})
140+
141+
export default {
142+
name: 'BVDIconsTable',
143+
data() {
144+
return {
145+
iconFilter: '',
146+
totalIcons: icons.length
147+
}
148+
},
149+
computed: {
150+
filteredIcons() {
151+
const terms = this.iconFilter
152+
.trim()
153+
.toLowerCase()
154+
.split(/\s+/)
155+
if (terms.length === 0) {
156+
return icons.slice()
157+
}
158+
return icons.filter(icon => terms.every(term => icon.name.indexOf(term) !== -1))
159+
}
160+
}
161+
}
162+
</script>

docs/components/importdoc.vue

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,9 @@
118118
</template>
119119

120120
<script>
121-
import hljs from '../utils/hljs'
122-
import kebabCase from 'lodash/kebabCase'
123121
import startCase from 'lodash/startCase'
122+
import hljs from '../utils/hljs'
123+
import { kebabCase } from '../utils'
124124
import AnchoredHeading from './anchored-heading'
125125
126126
const importPath = 'bootstrap-vue'
@@ -136,10 +136,11 @@ export default {
136136
return 'bootstrap-vue'
137137
},
138138
isComponentRoute() {
139-
return this.$route.name === 'docs-components-slug'
139+
const name = this.$route.name
140+
return name === 'docs-components-slug' || name === 'docs-icons'
140141
},
141142
pluginDir() {
142-
return this.$route.params.slug
143+
return this.$route.params.slug || this.meta.slug
143144
},
144145
pluginName() {
145146
// Directive plugin names are prefixed with `VB`
@@ -218,7 +219,7 @@ export default {
218219
},
219220
methods: {
220221
componentName(component) {
221-
return kebabCase(component)
222+
return kebabCase(component).replace('{', '-{')
222223
},
223224
componentTag(component) {
224225
return `<${this.componentName(component)}>`

docs/content/index.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { importAll, parseVersion } from '~/utils'
1+
import { importAll, parseVersion, parseFullVersion } from '~/utils'
22
import { version, dependencies, devDependencies, description } from '~/../package.json'
33
import DEFAULT_CONFIG from '~/../src/utils/config-defaults'
44

@@ -8,6 +8,18 @@ export const components = importAll(componentsContext)
88
const directivesContext = require.context('~/../src/directives/', true, /package.json/)
99
export const directives = importAll(directivesContext)
1010

11+
const iconsContext = require.context('~/../src/icons', false, /package.json/)
12+
const icons = importAll(iconsContext) || {}
13+
// Since there are over 300 icons, we only return the first BIcon component, plus one
14+
// extra example icon component which we modify the icon name to be `BIcon{IconName}`
15+
// We sort the array to ensure `BIcon` appears first
16+
icons[''].components = icons[''].components
17+
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
18+
.slice(0, 2)
19+
.map(c => ({ ...c }))
20+
icons[''].components[1].component = 'BIcon{IconName}'
21+
export { icons }
22+
1123
const referenceContext = require.context('~/markdown/reference', true, /meta.json/)
1224
export const reference = importAll(referenceContext)
1325

@@ -32,6 +44,13 @@ export const nav = [
3244
pages: directives,
3345
description: 'BootstrapVue directives and directive group plugins'
3446
},
47+
{
48+
title: 'Icons',
49+
base: 'icons',
50+
new: true,
51+
version: '2.3.0',
52+
description: 'BootstrapVue icons'
53+
},
3554
{
3655
title: 'Reference',
3756
base: 'reference/',
@@ -50,6 +69,7 @@ export const bootstrapVersion = parseVersion(dependencies.bootstrap)
5069
export const nuxtVersion = parseVersion(devDependencies.nuxt)
5170
export const portalVueVersion = parseVersion(dependencies['portal-vue'])
5271
export const vueVersion = parseVersion(devDependencies.vue)
72+
export const bootstrapIconsVersion = parseFullVersion(devDependencies['bootstrap-icons'])
5373
export const defaultConfig = DEFAULT_CONFIG
5474
export const bvDescription = description
5575

0 commit comments

Comments
 (0)