diff --git a/docs/assets/vercel.svg b/docs/assets/vercel.svg index 021ab2ae538..22f186cb965 100644 --- a/docs/assets/vercel.svg +++ b/docs/assets/vercel.svg @@ -1,3 +1,3 @@ - - - + + + diff --git a/src/components/aspect/README.md b/src/components/aspect/README.md index c2de9ead36e..e08abd0aece 100644 --- a/src/components/aspect/README.md +++ b/src/components/aspect/README.md @@ -1,65 +1,65 @@ -# Aspect - -> The `` component can be used to maintain a minimum responsive aspect ratio for content. -> When the content is longer than the available height, then the component will expand vertically to -> fit all content. If the content is shorter than the computed aspect height, the component will -> ensure a minimum height is maintained. - -## Overview - -The default [aspect]() ratio is `1:1` (ratio of -`1`), which makes the height always be at least the same as the width. The `aspect` prop can be used -to specify an arbitrary aspect ratio (i.e. `1.5`) or a ratio as a string such as `'16:9'` or -`'4:3'`. - -The width will always be 100% of the available width in the parent element/component. - -```html - - - - - -``` - -## See also - -- [`` component](/docs/components/embed) for responsive embeds (videos, iframes, etc) +# Aspect + +> The `` component can be used to maintain a minimum responsive aspect ratio for content. +> When the content is longer than the available height, then the component will expand vertically to +> fit all content. If the content is shorter than the computed aspect height, the component will +> ensure a minimum height is maintained. + +## Overview + +The default [aspect]() ratio is `1:1` (ratio of +`1`), which makes the height always be at least the same as the width. The `aspect` prop can be used +to specify an arbitrary aspect ratio (i.e. `1.5`) or a ratio as a string such as `'16:9'` or +`'4:3'`. + +The width will always be 100% of the available width in the parent element/component. + +```html + + + + + +``` + +## See also + +- [`` component](/docs/components/embed) for responsive embeds (videos, iframes, etc) diff --git a/src/components/aspect/aspect.js b/src/components/aspect/aspect.js index 21ca225e441..a2e06531e99 100644 --- a/src/components/aspect/aspect.js +++ b/src/components/aspect/aspect.js @@ -1,59 +1,59 @@ -import Vue from '../../utils/vue' -import { mathAbs } from '../../utils/math' -import { toFloat } from '../../utils/number' -import normalizeSlotMixin from '../../mixins/normalize-slot' - -// --- Constants --- -const NAME = 'BAspect' -const CLASS_NAME = 'b-aspect' - -const RX_ASPECT = /^\d+(\.\d*)?[/:]\d+(\.\d*)?$/ -const RX_SEPARATOR = /[/:]/ - -// --- Main Component --- -export const BAspect = /*#__PURE__*/ Vue.extend({ - name: NAME, - mixins: [normalizeSlotMixin], - props: { - aspect: { - // Accepts a number (i.e. `16 / 9`, `1`, `4 / 3`) - // Or a string (i.e. '16/9', '16:9', '4:3' '1:1') - type: [Number, String], - default: '1:1' - }, - tag: { - type: String, - default: 'div' - } - }, - computed: { - padding() { - const aspect = this.aspect - let ratio = 1 - if (RX_ASPECT.test(aspect)) { - // Width and/or Height can be a decimal value below `1`, so - // we only fallback to `1` if the value is `0` or `NaN` - const [width, height] = aspect.split(RX_SEPARATOR).map(v => toFloat(v) || 1) - ratio = width / height - } else { - ratio = toFloat(aspect) || 1 - } - return `${100 / mathAbs(ratio)}%` - } - }, - render(h) { - const $sizer = h('div', { - staticClass: `${CLASS_NAME}-sizer flex-grow-1`, - style: { paddingBottom: this.padding, height: 0 } - }) - const $content = h( - 'div', - { - staticClass: `${CLASS_NAME}-content flex-grow-1 w-100 mw-100`, - style: { marginLeft: '-100%' } - }, - [this.normalizeSlot('default')] - ) - return h(this.tag, { staticClass: `${CLASS_NAME} d-flex` }, [$sizer, $content]) - } -}) +import Vue from '../../utils/vue' +import { mathAbs } from '../../utils/math' +import { toFloat } from '../../utils/number' +import normalizeSlotMixin from '../../mixins/normalize-slot' + +// --- Constants --- +const NAME = 'BAspect' +const CLASS_NAME = 'b-aspect' + +const RX_ASPECT = /^\d+(\.\d*)?[/:]\d+(\.\d*)?$/ +const RX_SEPARATOR = /[/:]/ + +// --- Main Component --- +export const BAspect = /*#__PURE__*/ Vue.extend({ + name: NAME, + mixins: [normalizeSlotMixin], + props: { + aspect: { + // Accepts a number (i.e. `16 / 9`, `1`, `4 / 3`) + // Or a string (i.e. '16/9', '16:9', '4:3' '1:1') + type: [Number, String], + default: '1:1' + }, + tag: { + type: String, + default: 'div' + } + }, + computed: { + padding() { + const aspect = this.aspect + let ratio = 1 + if (RX_ASPECT.test(aspect)) { + // Width and/or Height can be a decimal value below `1`, so + // we only fallback to `1` if the value is `0` or `NaN` + const [width, height] = aspect.split(RX_SEPARATOR).map(v => toFloat(v) || 1) + ratio = width / height + } else { + ratio = toFloat(aspect) || 1 + } + return `${100 / mathAbs(ratio)}%` + } + }, + render(h) { + const $sizer = h('div', { + staticClass: `${CLASS_NAME}-sizer flex-grow-1`, + style: { paddingBottom: this.padding, height: 0 } + }) + const $content = h( + 'div', + { + staticClass: `${CLASS_NAME}-content flex-grow-1 w-100 mw-100`, + style: { marginLeft: '-100%' } + }, + [this.normalizeSlot('default')] + ) + return h(this.tag, { staticClass: `${CLASS_NAME} d-flex` }, [$sizer, $content]) + } +}) diff --git a/src/components/aspect/aspect.spec.js b/src/components/aspect/aspect.spec.js index 60bcbc552e6..6813631451e 100644 --- a/src/components/aspect/aspect.spec.js +++ b/src/components/aspect/aspect.spec.js @@ -1,121 +1,121 @@ -import { mount } from '@vue/test-utils' -import { BAspect } from './aspect' - -describe('aspect', () => { - it('should have expected default structure', async () => { - const wrapper = mount(BAspect) - - expect(wrapper.vm).toBeDefined() - expect(wrapper.element.tagName).toBe('DIV') - expect(wrapper.classes()).toContain('b-aspect') - expect(wrapper.classes()).toContain('d-flex') - expect(wrapper.classes().length).toBe(2) - - const $sizer = wrapper.find('.b-aspect-sizer') - expect($sizer.exists()).toBe(true) - expect($sizer.element.tagName).toBe('DIV') - expect($sizer.classes()).toContain('flex-grow-1') - // Default aspect ratio is 1:1 - expect($sizer.attributes('style')).toContain('padding-bottom: 100%;') - - const $content = wrapper.find('.b-aspect-content') - expect($content.exists()).toBe(true) - expect($content.element.tagName).toBe('DIV') - expect($content.classes()).toContain('flex-grow-1') - expect($content.classes()).toContain('w-100') - expect($content.classes()).toContain('mw-100') - expect($content.attributes('style')).toContain('margin-left: -100%;') - - wrapper.destroy() - }) - - it('should have expected structure when prop `tag` is set', async () => { - const wrapper = mount(BAspect, { - propsData: { - tag: 'section' - } - }) - - expect(wrapper.vm).toBeDefined() - expect(wrapper.element.tagName).toBe('SECTION') - expect(wrapper.classes()).toContain('b-aspect') - expect(wrapper.classes()).toContain('d-flex') - expect(wrapper.classes().length).toBe(2) - - const $sizer = wrapper.find('.b-aspect-sizer') - expect($sizer.exists()).toBe(true) - expect($sizer.element.tagName).toBe('DIV') - expect($sizer.classes()).toContain('flex-grow-1') - // Default aspect ratio is 1:1 - expect($sizer.attributes('style')).toContain('padding-bottom: 100%;') - - const $content = wrapper.find('.b-aspect-content') - expect($content.exists()).toBe(true) - expect($content.element.tagName).toBe('DIV') - expect($content.classes()).toContain('flex-grow-1') - expect($content.classes()).toContain('w-100') - expect($content.classes()).toContain('mw-100') - expect($content.attributes('style')).toContain('margin-left: -100%;') - - wrapper.destroy() - }) - - it('should have expected structure when aspect is set to "4:3"', async () => { - const wrapper = mount(BAspect, { - propsData: { - aspect: '4:3' - } - }) - - expect(wrapper.vm).toBeDefined() - expect(wrapper.element.tagName).toBe('DIV') - expect(wrapper.classes()).toContain('b-aspect') - expect(wrapper.classes()).toContain('d-flex') - expect(wrapper.classes().length).toBe(2) - - const $sizer = wrapper.find('.b-aspect-sizer') - expect($sizer.exists()).toBe(true) - expect($sizer.element.tagName).toBe('DIV') - expect($sizer.classes()).toContain('flex-grow-1') - expect($sizer.attributes('style')).toContain('padding-bottom: 75%;') - - const $content = wrapper.find('.b-aspect-content') - expect($content.exists()).toBe(true) - expect($content.element.tagName).toBe('DIV') - expect($content.classes()).toContain('flex-grow-1') - expect($content.classes()).toContain('w-100') - expect($content.classes()).toContain('mw-100') - expect($content.attributes('style')).toContain('margin-left: -100%;') - - wrapper.destroy() - }) - it('should have expected structure when aspect is set to `16/9`', async () => { - const wrapper = mount(BAspect, { - propsData: { - aspect: 16 / 9 - } - }) - - expect(wrapper.vm).toBeDefined() - expect(wrapper.element.tagName).toBe('DIV') - expect(wrapper.classes()).toContain('b-aspect') - expect(wrapper.classes()).toContain('d-flex') - expect(wrapper.classes().length).toBe(2) - - const $sizer = wrapper.find('.b-aspect-sizer') - expect($sizer.exists()).toBe(true) - expect($sizer.element.tagName).toBe('DIV') - expect($sizer.classes()).toContain('flex-grow-1') - expect($sizer.attributes('style')).toContain('padding-bottom: 56.25%;') - - const $content = wrapper.find('.b-aspect-content') - expect($content.exists()).toBe(true) - expect($content.element.tagName).toBe('DIV') - expect($content.classes()).toContain('flex-grow-1') - expect($content.classes()).toContain('w-100') - expect($content.classes()).toContain('mw-100') - expect($content.attributes('style')).toContain('margin-left: -100%;') - - wrapper.destroy() - }) -}) +import { mount } from '@vue/test-utils' +import { BAspect } from './aspect' + +describe('aspect', () => { + it('should have expected default structure', async () => { + const wrapper = mount(BAspect) + + expect(wrapper.vm).toBeDefined() + expect(wrapper.element.tagName).toBe('DIV') + expect(wrapper.classes()).toContain('b-aspect') + expect(wrapper.classes()).toContain('d-flex') + expect(wrapper.classes().length).toBe(2) + + const $sizer = wrapper.find('.b-aspect-sizer') + expect($sizer.exists()).toBe(true) + expect($sizer.element.tagName).toBe('DIV') + expect($sizer.classes()).toContain('flex-grow-1') + // Default aspect ratio is 1:1 + expect($sizer.attributes('style')).toContain('padding-bottom: 100%;') + + const $content = wrapper.find('.b-aspect-content') + expect($content.exists()).toBe(true) + expect($content.element.tagName).toBe('DIV') + expect($content.classes()).toContain('flex-grow-1') + expect($content.classes()).toContain('w-100') + expect($content.classes()).toContain('mw-100') + expect($content.attributes('style')).toContain('margin-left: -100%;') + + wrapper.destroy() + }) + + it('should have expected structure when prop `tag` is set', async () => { + const wrapper = mount(BAspect, { + propsData: { + tag: 'section' + } + }) + + expect(wrapper.vm).toBeDefined() + expect(wrapper.element.tagName).toBe('SECTION') + expect(wrapper.classes()).toContain('b-aspect') + expect(wrapper.classes()).toContain('d-flex') + expect(wrapper.classes().length).toBe(2) + + const $sizer = wrapper.find('.b-aspect-sizer') + expect($sizer.exists()).toBe(true) + expect($sizer.element.tagName).toBe('DIV') + expect($sizer.classes()).toContain('flex-grow-1') + // Default aspect ratio is 1:1 + expect($sizer.attributes('style')).toContain('padding-bottom: 100%;') + + const $content = wrapper.find('.b-aspect-content') + expect($content.exists()).toBe(true) + expect($content.element.tagName).toBe('DIV') + expect($content.classes()).toContain('flex-grow-1') + expect($content.classes()).toContain('w-100') + expect($content.classes()).toContain('mw-100') + expect($content.attributes('style')).toContain('margin-left: -100%;') + + wrapper.destroy() + }) + + it('should have expected structure when aspect is set to "4:3"', async () => { + const wrapper = mount(BAspect, { + propsData: { + aspect: '4:3' + } + }) + + expect(wrapper.vm).toBeDefined() + expect(wrapper.element.tagName).toBe('DIV') + expect(wrapper.classes()).toContain('b-aspect') + expect(wrapper.classes()).toContain('d-flex') + expect(wrapper.classes().length).toBe(2) + + const $sizer = wrapper.find('.b-aspect-sizer') + expect($sizer.exists()).toBe(true) + expect($sizer.element.tagName).toBe('DIV') + expect($sizer.classes()).toContain('flex-grow-1') + expect($sizer.attributes('style')).toContain('padding-bottom: 75%;') + + const $content = wrapper.find('.b-aspect-content') + expect($content.exists()).toBe(true) + expect($content.element.tagName).toBe('DIV') + expect($content.classes()).toContain('flex-grow-1') + expect($content.classes()).toContain('w-100') + expect($content.classes()).toContain('mw-100') + expect($content.attributes('style')).toContain('margin-left: -100%;') + + wrapper.destroy() + }) + it('should have expected structure when aspect is set to `16/9`', async () => { + const wrapper = mount(BAspect, { + propsData: { + aspect: 16 / 9 + } + }) + + expect(wrapper.vm).toBeDefined() + expect(wrapper.element.tagName).toBe('DIV') + expect(wrapper.classes()).toContain('b-aspect') + expect(wrapper.classes()).toContain('d-flex') + expect(wrapper.classes().length).toBe(2) + + const $sizer = wrapper.find('.b-aspect-sizer') + expect($sizer.exists()).toBe(true) + expect($sizer.element.tagName).toBe('DIV') + expect($sizer.classes()).toContain('flex-grow-1') + expect($sizer.attributes('style')).toContain('padding-bottom: 56.25%;') + + const $content = wrapper.find('.b-aspect-content') + expect($content.exists()).toBe(true) + expect($content.element.tagName).toBe('DIV') + expect($content.classes()).toContain('flex-grow-1') + expect($content.classes()).toContain('w-100') + expect($content.classes()).toContain('mw-100') + expect($content.attributes('style')).toContain('margin-left: -100%;') + + wrapper.destroy() + }) +}) diff --git a/src/components/aspect/index.d.ts b/src/components/aspect/index.d.ts index d2113036ca6..c084f67bdc0 100644 --- a/src/components/aspect/index.d.ts +++ b/src/components/aspect/index.d.ts @@ -1,11 +1,11 @@ -// -// Aspect -// -import Vue from 'vue' -import { BvPlugin, BvComponent } from '../../' - -// Plugin -export declare const AspectPlugin: BvPlugin - -// Component: b-aspect -export declare class BAspect extends BvComponent {} +// +// Aspect +// +import Vue from 'vue' +import { BvPlugin, BvComponent } from '../../' + +// Plugin +export declare const AspectPlugin: BvPlugin + +// Component: b-aspect +export declare class BAspect extends BvComponent {} diff --git a/src/components/aspect/index.js b/src/components/aspect/index.js index 82ca1cf7233..40b22eed368 100644 --- a/src/components/aspect/index.js +++ b/src/components/aspect/index.js @@ -1,8 +1,8 @@ -import { BAspect } from './aspect' -import { pluginFactory } from '../../utils/plugins' - -const AspectPlugin = /*#__PURE__*/ pluginFactory({ - components: { BAspect } -}) - -export { AspectPlugin, BAspect } +import { BAspect } from './aspect' +import { pluginFactory } from '../../utils/plugins' + +const AspectPlugin = /*#__PURE__*/ pluginFactory({ + components: { BAspect } +}) + +export { AspectPlugin, BAspect } diff --git a/src/components/aspect/package.json b/src/components/aspect/package.json index 5a3440f7a3e..cb585bdd58f 100644 --- a/src/components/aspect/package.json +++ b/src/components/aspect/package.json @@ -1,21 +1,21 @@ -{ - "name": "@bootstrap-vue/aspect", - "version": "1.0.0", - "meta": { - "title": "Aspect", - "new": true, - "version": "2.9.0", - "description": "The `` component can be used to maintain a minimum responsive aspect ratio for content.", - "components": [ - { - "component": "BAspect", - "props": [ - { - "prop": "aspect", - "description": "Aspect as a width to height numeric ratio (such as `1.5`) or `width:height` string (such as '16:9')" - } - ] - } - ] - } -} +{ + "name": "@bootstrap-vue/aspect", + "version": "1.0.0", + "meta": { + "title": "Aspect", + "new": true, + "version": "2.9.0", + "description": "The `` component can be used to maintain a minimum responsive aspect ratio for content.", + "components": [ + { + "component": "BAspect", + "props": [ + { + "prop": "aspect", + "description": "Aspect as a width to height numeric ratio (such as `1.5`) or `width:height` string (such as '16:9')" + } + ] + } + ] + } +} diff --git a/src/components/avatar/README.md b/src/components/avatar/README.md index 61f2e26d271..be5f07d967a 100644 --- a/src/components/avatar/README.md +++ b/src/components/avatar/README.md @@ -1,567 +1,567 @@ -# Avatar - -> Avatars are a BootstrapVue custom component, and are typically used to display a user profile as a -> picture, an icon, or short text. `` provides several props for customizing its -> appearance such as color variant and roundness, and optionally supports acting as a button, link -> or [router link](/docs/reference/router-links). - -## Overview - -Avatars are lightweight components, which render inline by default, so that they are vertically -centered beside any adjoining plain text. They also can be used as children of other components. - -```html - - - -``` - -## Avatar types - -The avatar content can be either a an image, an icon, or short text string. Avatar content defaults -to the [`'person-fill'` icon](/docs/icons) when no other content is specified. - -You can also supply custom content via the default slot, although you may need to apply additional -styling on the content. - -### Image content - -Use the `src` prop to specify a URL of an image to use as the avatar content. The image should have -an aspect ratio of `1:1` (meaning the width and height should be equal), otherwise image aspect -distortion will occur. The image will be scaled up or down to fit within the avatar's bounding box. - -```html - - - -``` - -**Notes:** - -- When using a module bundler and project relative image URLs, please refer to the - [Component img src resolving](/docs/reference/images) reference section for additional details. -- The `src` prop takes precedence over the `icon` and `text` props. -- 2.11.0+ If the image fails to load, the avatar will - fallback to the value of the `icon` or `text` props. If neither the `icon` or `text` props are - provided, then the default avatar icon will be shown. Also, when the image fails to load, the - `img-error` event will be emitted. -- [Variant colors](#variants) when using images not normally visible, unless the image fails load. - The variant will affect the focus styling when the image avatar is also an - [actionalble avatar](#actionalble-avatars). - -### Icon content - -Easily use one of [BootstrapVue's icons](/docs/icons) as the avatar content via the `icon` prop. The -prop should be set to a valid icon name. Icons will scale respective to the [`size` prop](#sizing). - -```html - - - -``` - -**Notes:** - -- When providing a BootstrapVue icon name, you _must_ ensure that you have registered the - corresponding icon component (either locally to your component/page, or globally), if not using - the full [`BootstrapVueIcons` plugin](/docs/icons). -- The `icon` prop takes precedence over the `text` prop. -- If the `text`, `src`, or `icon` props are not provided _and_ the [default slot](#custom-content) - has no content, then the `person-fill` icon will be used. - -### Text content - -You can specify a short string as the content of an avatar via the `text` prop. The string should be -short (1 to 3 characters), and will be transformed via CSS to be all uppercase. The font size will -be scaled relative to the [`size` prop setting](#sizing). - -```html - - - -``` - -### Custom content - -Use the `default` slot to render custom content in the avatar, for finer grained control of its -appearance, or if using custom icons or SVGs e.g.: - -```html - -``` - -**Multi-line text example:** - -```html - - - -``` - -**Notes:** - -- The default slot takes precedence over the `text`, `src` and `icon` props. -- The default slot content will be wrapped in a `` element to ensure proper centering. -- You may need additional styling applied to the custom content to compensate for the - [shape of avatar component](#rounding). - -## Styling - -### Variants - -Use the `variant` prop to specify one of Bootstrap theme variant colors. The default variant is -`secondary`. - -```html - - - -``` - -If you have defined additional custom variants via -[SASS theming variables](/docs/reference/theming), the custom variants will also be available to -use. - -### Sizing - -By default, avatars are sized to `2.5em` (which is relative to the current font size). You can -change the size of the avatar by changing the current font size, or use the prop `size` to specify -an explicit size. The sizes `sm`, `md` and `lg` default to `1.5em`, `2.5em` and `3.5em`. Numbers get -converted to pixel values. Any other value _must_ include the units (such as `px`, `em`, or `rem`). - -```html - - - -``` - -**Note:** Avatars are _always_ rendered with an aspect ratio of `1:1`. - -### Square - -Prefer a square avatar? simply set the `square` prop to `true`. - -```html - - - -``` - -### Rounding - -`` renders with a circular border radius. You can change the rounding by setting the prop -`rounded` to one of the values `true`, `'sm'`, `'lg'`, `'top'`, `'left'`, `'right'`, or `'bottom'`. -When set to `true` (or the empty string `''`), it uses the Bootstrap default of medium rounding. - -```html - - - -``` - -**Notes:** - -- The `square` prop takes precedence over the `rounded` prop. -- Alternatively to to the `square` prop, you can set the `rounded` prop to the string `'0'` to - achieve a square avatar. - -### Alignment - -By default `` will be vertically centered with its adjoining content. In some cases you -may want to alter the alignment, such as ensuring that a text-only avatar aligns its text with the -adjoining text. Simply set a [vertical alignment utility](/docs/reference/utility-classes) class on -the component, such as `` or -``, etc. - -## Actionable avatars - -Easily create avatars that respond to clicks, or avatars that change the URL/route when clicked. -Actionable avatars will appear in the document tab sequence, and are accessible for both screen -reader and keyboard-only users. - -Image avatars, when actionalble, employ a basic scale transform on the image when hovered. - -### Button - -Want to trigger the opening of a modal or trigger an action? Set the `button` prop to instruct -`` to render as a `