Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(b-row): add Bootstrap v4.4 row columns support #4439

Merged
merged 17 commits into from
Dec 2, 2019
99 changes: 90 additions & 9 deletions src/components/layout/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,10 @@ like the

## Containers `<b-container>`

Containers (`<b-container>`) are the most basic layout element in Bootstrap and is **required when
using the grid system**. Choose from a responsive, fixed-width container (meaning its max-width
changes at each breakpoint) by default, or fluid-width (meaning it's 100% wide all the time) by
setting 'fluid' prop, or responsive containers where the container is fluid up until a specific
breakpoint.
Containers (`<b-container>`) are the most basic layout element in Bootstrap. Choose from a
responsive, fixed-width container (meaning its max-width changes at each breakpoint) by default, or
fluid-width (meaning it's 100% wide all the time) by setting 'fluid' prop, or responsive containers
where the container is fluid up until a specific breakpoint (requires Bootstrap CSS `v4.4+`).

While containers can be nested, most layouts do not require a nested container.

Expand All @@ -85,7 +84,7 @@ The default breakpoint widths can be configured using Bootstrap V4.x SCSS variab
### Default container

The default `<b-container>` is a responsive, fixed-width container, meaning its `max-width` changes
at each breakpoint.
at each viewport width breakpoint.

```html
<b-container>
Expand All @@ -109,7 +108,7 @@ Setting the `fluid` prop to true (or an empty string) is equivalent to the Boots

### Responsive fluid containers

<span class="badge badge-warning small">Requires Bootstrap v4.4+ CSS</span>
<span class="badge badge-info small">Requires Bootstrap v4.4+ CSS</span>

Responsive containers are new in Bootstrap v4.4. They allow you to specify a container that is 100%
wide (fluid) until particular breakpoint is reached at which point a `max-width` is applied. For
Expand Down Expand Up @@ -137,8 +136,9 @@ Setting the fluid prop to a breakpoint name translates to the Bootstrap class

## Rows `<b-row>` and `<b-form-row>`

`<b-row>` components should be placed inside a `<b-container>` component, or an element (such as a
`<div>`) that has the class `container` or `container-fluid` applied to it.
Rows are wrappers for [columns](#columns-b-col). Each column has horizontal padding (called a
gutter) for controlling the space between them. This padding is then counteracted on the rows with
negative margins. This way, all the content in your columns is visually aligned down the left side.

You can remove the margin from `<b-row>` and padding from `<b-col>` by setting the `no-gutters` prop
on `<b-row>`.
Expand Down Expand Up @@ -622,4 +622,85 @@ within an existing `<b-col>` component. Nested rows should include a set of colu
<!-- b-grid-nesting.vue -->
```

## Row columns

<span class="badge badge-info small">Requires Bootstrap v4.4+ CSS</span>

Use the responsive `cols-*` props in `<b-row>` to quickly set the number of columns that best render
your content and layout. Whereas normal column widths are apply to the individual `<b-col>` columns
(e.g., `<b-col md="4">`), the row columns `col-*` props are set on the parent `<b-row>` as a
shortcut.

Use these row columns to quickly create basic grid layouts or to control your card layouts. The
default maximum number of row columns in Bootstrap v4.4 is `6` (unlike the regular columns which
have a default maximum of `12` columns)

The value specified in the `<b-row>` prop(s) is the number of columns to create per row (whereas the
props on `<b-col>` refer to the number of columns to occupy).

```html
<b-container class="bv-example-row">
<b-row cols="2">
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
</b-row>
</b-container>

<b-container class="bv-example-row">
<b-row cols="3">
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
</b-row>
</b-container>

<b-container class="bv-example-row">
<b-row cols="4">
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
</b-row>
</b-container>

<b-container class="bv-example-row">
<b-row cols="4">
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col cols="6">Column</b-col>
<b-col>Column</b-col>
</b-row>
</b-container>

<!-- b-grid-row-cols-introduction.vue -->
```

You can control the number of columns at each breakpoint level via the following `<b-row>` props:

- `cols` for `xs` and up screens
- `cols-sm` for `sm` and up screens
- `cols-md` for `md` and up screens
- `cols-lg` for `lg` and up screens
- `cols-xl` for `xl` and up screens

```html
<b-container class="bv-example-row">
<b-row cols="1" cols-sm="2" cols-md="4" cols-lg="6" cols-xl="8">
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
<b-col>Column</b-col>
</b-row>
</b-container>

<!-- b-grid-row-cols-breakpoints.vue -->
```

<!-- Component reference added automatically from component package.json -->
9 changes: 5 additions & 4 deletions src/components/layout/col.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import identity from '../../utils/identity'
import memoize from '../../utils/memoize'
import suffixPropName from '../../utils/suffix-prop-name'
import { arrayIncludes } from '../../utils/array'
import { isUndefinedOrNull } from '../../utils/inspect'
import { keys, assign, create } from '../../utils/object'
import { getBreakpointsUpCached } from '../../utils/config'
import { isUndefinedOrNull } from '../../utils/inspect'
import { assign, create, keys } from '../../utils/object'
import { lowerCase } from '../../utils/string'

const RX_COL_CLASS = /^col-/

Expand Down Expand Up @@ -35,11 +36,11 @@ const computeBreakpoint = (type, breakpoint, val) => {
// Since the default is false, an empty string indicates the prop's presence.
if (type === 'col' && (val === '' || val === true)) {
// .col-md
return className.toLowerCase()
return lowerCase(className)
}
// .order-md-6
className += `-${val}`
return className.toLowerCase()
return lowerCase(className)
}

// Memoized function for better performance on generating class names
Expand Down
27 changes: 26 additions & 1 deletion src/components/layout/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"props": [
{
"prop": "fluid",
"description": "When set to true, makes the row 100% wide all the time, or set to one of the Bootstrap breakpoint names for 100% width up to the breakpoint"
"description": "When set to true, makes the row 100% wide all the time, or set to one of the Bootstrap breakpoint names for 100% width up to the breakpoint (requires Bootstrap v4.4+ CSS for breakpoint specific value)"
}
]
},
Expand All @@ -33,6 +33,31 @@
{
"prop": "alignContent",
"description": "Align columns items together on the cross axis: 'start', 'center', 'end', 'around', 'between' or 'stretch'. Has no effect on single rows of items"
},
{
"prop": "cols",
"version": "2.2.0",
"description": "The number row columns to create at the 'xs' breakpoint. Requires Bootstrap v4.4 CSS"
},
{
"prop": "cols-sm",
"version": "2.2.0",
"description": "The number row columns to create at the 'sm' breakpoint. Requires Bootstrap v4.4 CSS"
},
{
"prop": "cols-md",
"version": "2.2.0",
"description": "The number row columns to create at the 'md' breakpoint. Requires Bootstrap v4.4 CSS"
},
{
"prop": "cols-lg",
"version": "2.2.0",
"description": "The number row columns to create at the 'lg' breakpoint. Requires Bootstrap v4.4 CSS"
},
{
"prop": "cols-xl",
"version": "2.2.0",
"description": "The number row columns to create at the 'xl' breakpoint. Requires Bootstrap v4.4 CSS"
}
]
},
Expand Down
137 changes: 97 additions & 40 deletions src/components/layout/row.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,110 @@
import Vue from '../../utils/vue'
import { mergeData } from 'vue-functional-data-merge'
import identity from '../../utils/identity'
import memoize from '../../utils/memoize'
import suffixPropName from '../../utils/suffix-prop-name'
import { arrayIncludes } from '../../utils/array'
import { getBreakpointsUpCached } from '../../utils/config'
import { create, keys } from '../../utils/object'
import { lowerCase, toString, trim } from '../../utils/string'

const COMMON_ALIGNMENT = ['start', 'end', 'center']

export const props = {
tag: {
type: String,
default: 'div'
},
noGutters: {
type: Boolean,
default: false
},
alignV: {
type: String,
default: null,
validator: str => arrayIncludes(COMMON_ALIGNMENT.concat(['baseline', 'stretch']), str)
},
alignH: {
type: String,
default: null,
validator: str => arrayIncludes(COMMON_ALIGNMENT.concat(['between', 'around']), str)
},
alignContent: {
type: String,
default: null,
validator: str => arrayIncludes(COMMON_ALIGNMENT.concat(['between', 'around', 'stretch']), str)
// Generates a prop object with a type of `[String, Number]`
const strNum = () => ({
type: [String, Number],
default: null
})

// Compute a `row-cols-{breakpoint}-{cols}` class name
// Memoized function for better performance on generating class names
const computeRowColsClass = memoize((breakpoint, cols) => {
cols = trim(toString(cols))
return cols ? lowerCase(['row-cols', breakpoint, cols].filter(identity).join('-')) : null
})

// Get the breakpoint name from the `rowCols` prop name
// Memoized function for better performance on extracting breakpoint names
const computeRowColsBreakpoint = memoize(prop => lowerCase(prop.replace('cols', '')))

// Cached copy of the `row-cols` breakpoint prop names
// Will be populated when the props are generated
let rowColsPropList = []

// Lazy evaled props factory for <b-row> (called only once,
// the first time the component is used)
const generateProps = () => {
// Grab the breakpoints from the cached config (including the '' (xs) breakpoint)
const breakpoints = getBreakpointsUpCached()

// Supports classes like: `row-cols-2`, r`ow-cols-md-4`, `row-cols-xl-6`
const rowColsProps = breakpoints.reduce((props, breakpoint) => {
props[suffixPropName(breakpoint, 'cols')] = strNum()
return props
}, create(null))

// Cache the row-cols prop names
rowColsPropList = keys(rowColsProps)

// Return the generated props
return {
tag: {
type: String,
default: 'div'
},
noGutters: {
type: Boolean,
default: false
},
alignV: {
type: String,
default: null,
validator: str => arrayIncludes(COMMON_ALIGNMENT.concat(['baseline', 'stretch']), str)
},
alignH: {
type: String,
default: null,
validator: str => arrayIncludes(COMMON_ALIGNMENT.concat(['between', 'around']), str)
},
alignContent: {
type: String,
default: null,
validator: str =>
arrayIncludes(COMMON_ALIGNMENT.concat(['between', 'around', 'stretch']), str)
},
...rowColsProps
}
}

// We do not use `Vue.extend()` here as that would evaluate the props
// immediately, which we do not want to happen
// @vue/component
export const BRow = /*#__PURE__*/ Vue.extend({
export const BRow = {
name: 'BRow',
functional: true,
props,
get props() {
// Allow props to be lazy evaled on first access and
// then they become a non-getter afterwards
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get#Smart_self-overwriting_lazy_getters
delete this.props
this.props = generateProps()
return this.props
},
render(h, { props, data, children }) {
return h(
props.tag,
mergeData(data, {
staticClass: 'row',
class: {
'no-gutters': props.noGutters,
[`align-items-${props.alignV}`]: props.alignV,
[`justify-content-${props.alignH}`]: props.alignH,
[`align-content-${props.alignContent}`]: props.alignContent
}
}),
children
)
const classList = []
// Loop through row-cols breakpoint props and generate the classes
rowColsPropList.forEach(prop => {
const c = computeRowColsClass(computeRowColsBreakpoint(prop), props[prop])
// If a class is returned, push it onto the array
if (c) {
classList.push(c)
}
})
classList.push({
'no-gutters': props.noGutters,
[`align-items-${props.alignV}`]: props.alignV,
[`justify-content-${props.alignH}`]: props.alignH,
[`align-content-${props.alignContent}`]: props.alignContent
})
return h(props.tag, mergeData(data, { staticClass: 'row', class: classList }), children)
}
})
}
Loading