@boite-beet/ui-kit
v1.2.4
Published
Beet's UI development kit
Downloads
4
Readme
BEET UI Kit
Beet's Vue UI development kit and component library
Installation
In an existing project, open a command line at the root level and run:
yarn add @boite-beet/ui-kit
Then, in src/main.js
initialize through Vue's plugin api:
// src/main.js
import BeetUI from '@boite-beet/ui-kit'
Vue.use(BeetUI)
By importing the library's styles before your own, you ensure that any overridden selectors will take precedence. In theory you can import styles from any script, but it should be noted that in our case this means they will be injected after your own styles and may override your selectors.
Basic Usage
There are two main resources you can import to your project: components and styles. All available components are exposed by the package's main export whereas all Sass stylesheets can be imported from the styles
subfolder. For example:
// NavBar.vue <script>
import { DropDown } from '@boite-beet/ui-kit'
export default {
name: 'NavBar',
components: { DropDown }
// ...
}
// src/styles/index.js
import '@boite-beet/ui-kit/styles/globals.scss'
import '@boite-beet/ui-kit/styles/dropdown.scss'
import './app.scss'
Components
The package's default export is the Vue install method which registers some components globally. These do not need to be imported individually, just use them as you would use the router's <RouterLink>
.
Element [global]
Like Vue's built-in <component :is="DynamicComponent"/>
but for HTML elements instead of Vue components. Useful for cases where you may need to dynamically change the HTML tag used to render a part of your content.
Props
| Prop | Type | Default | Description |
| ---- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------ |
| is | String | 'div'
| The HTML tag to use. |
| tag | String | null
| The same as is
prop, needed for cases where Element
is used with Vue's <component :is="Element">
to avoid prop conflict. |
Usage
<template>
<element :is="dynamicTag">Sup</element>
<!-- or -->
<Component :is="Element" :tag="dynamicTag">Oh hey there!</Component>
</template>
<script>
export default {
computed: {
dynamicTag() {
return this.condition ? 'ul' : 'div'
}
}
}
</script>
Icon [global]
Used to render icons that are included in the icons.svg
spritesheet that is generated at build time from the SVG
files found in your project's src/assets/icons
folder.
Props
| Prop | Type | Default | Description |
| ---- | ------ | ---------- | ------------------------------------------------------------------------------------------- |
| id | String | required | The id of the icon to render. The id is based on the icon's filename in src/assets/icons
. |
Usage
<template>
<button>
<Icon id="trash" />
Delete
</button>
</template>
RawHtml [global]
Used to safely inject a sanitized HTML string with additional formatting controls.
Props
| Prop | Type | Default | Description |
| ----------------- | -------------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| tag | String | 'div'
| The tag with which the content should be wrapped. |
| html | String, Object | {}
| An HTML string or an object with at least one of the keys { before, after }
. See details below for usage pattern. |
| targetBlankLinks | Boolean | false
| Automatically add target="_blank" rel="noopener"
to anchor tags if true
. |
| preventImages | Boolean | false
| Disallow img
tags in content if true
. |
| replace | Array | []
| An array of replacements to apply. See details below for usage pattern. |
| allowedTags | Array | []
| An array of strings representing tags to allow. See sanitize-html details below. |
| allowedAttributes | Object | {}
| An object defining allowed HTML attributes per tag. See sanitize-html details below. |
Details
html
prop
If an object is passed, it should contain one or both accepted keys before
and after
. These determine where the content will be injected much in the same manner as CSS' ::before, ::after
. This way, you can also pass your own content to the default slot and decide if the HTML should be placed before or after your content. This can be useful for titles (placing content after
) and buttons/links (placing content before
).
If both are passed, both will be rendered in order.
If a string is passed instead of an object, it will default to the after
position.
replace
prop
Allows manipulation of the HTML string before it is converted to HTML and rendered. Accepts an array of values that follow these patterns:
- A string (
:replace="['dude']"
) which will remove all instances of the value. Note that the string is converted to a regular expression, so all regex expressions should be escaped, such as parentheses for capture groups:'my \(favorite\) dude'
. - A regular expression (
:replace="[/wut wut/g]"
) which will remove the match. Note that only the first match will be removed if theg
flag is not set. - A tuple (array of two values) where the first value is a string or regular expression (as above) and the second is one of the following, used for replacement. See the String replace MDN docs for more detail (the values will be passed in order to
String.replace
).- A string (
:replace="[[/(<br\s?\/?>){2,}/g, '<br>']]"
). The example here would replace two or more consecutive<br> or <br/> or <br />
tags by a single<br>
. - A function (
:replace="[/\s(i)\s/g, ($m, $1) => ` ${$1.toUpperCase()} `]"
). The example here would replace'how i feel when it rains'
with'how I feel when it rains'
.
- A string (
allowedTags
/allowedAttributes
props
These will be merged into the sanitize-html configuration object. For details on how to configure, see the sanitize-html docs for more information.
Usage
<template>
<RawHtml
tag="section"
:html="{ before: htmlContent }"
:replace="[['what', 'wut wut in the butt']]"
target-blank-links
prevent-images
>
<RouterLink :to="{ name: 'home' }">Go Home</RouterLink>
</RawHtml>
</template>
WrapHtml [global]
This is an alternative to RawHtml
that allows you to pass the HTML string as content instead of a prop. It simply takes the slot content and passes it to RawHtml
as a prop for you.
Props
The same as RawHtml
except for the html
prop which is replaced by the default slot's content.
Usage
<template>
<WrapHtml tag="section" :replace="[['what', 'wut wut in the butt']]" target-blank-links prevent-images>
{{ htmlContent }}
</WrapHtml>
</template>
DropDown
Allows you to transition between a height of 0
and auto
. Useful for dropdown menus or accordion content.
Props
| Prop | Type | Default | Description |
| ------------- | ----------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
| isOpen | Boolean | required | Determines the content's visibility. Collapsed when false
. |
| tag | String | 'div'
| The HTML tag to be used when rendering the outer element. |
| contentTag | String | 'div'
| The HTML tag to be used when rendering the content wrapper element. |
| contentClass | String, Array, Object | []
| Additional classnames to be applied to the content wrapper element. |
| transitionKey | String, Number, Array, Object | null
| Similar to Vue's usage of the key
prop, can be used to indicate when the height will change and transition to the new height. |
Events
| Name | Payload | Description |
| -------------- | ----------- | ------------------------------------------------- |
| transition-end | undefined
| Is emitted when an open or close transition ends. |
Usage
// src/styles/index.js
import '@boite-beet/ui-kit/styles/globals.scss'
import '@boite-beet/ui-kit/styles/dropdown.scss' // import before your styles
import './app.scss'
<template>
<nav class="nav">
<ul class="nav__menu">
<li class="nav__meta">
<span @click="isAccountOpen = !isAccountOpen">Account</span>
<DropDown :is-open="isAccountOpen" content-class="nav__tray">
<RouterLink :to="{ name: 'user-home', params: { userId } }">My Account</RouterLink>
<RouterLink :to="{ name: 'user-settings', params: { userId } }">Settings</RouterLink>
<RouterLink :to="{ name: 'logout' }">Log out</RouterLink>
</DropDown>
</li>
</ul>
</nav>
</template>
<script>
import { DropDown } from '@boite-beet/ui-kit'
export default {
name: 'NavBar',
components: { DropDown },
data() {
return {
userId: 'udy123',
isAccountOpen: false
}
}
}
</script>
Content Components
Base content components (ContentText, ContentSplit, etc.) can be imported individually or grouped as a named export { ContentComponents }
for ease of use:
<script>
import { ContentComponents } from '@boite-beet/ui-kit'
export default {
components: { ...ContentComponents }
}
</script>
Shared props
All content components share the following props:
| Prop | Type | Default | Description |
| ---------- | ------ | ------- | -------------------------------------------------------------------------------------- |
| classNames | Object | {}
| A map of class names you may wish to add to a component's children. See details below. |
Details
Conditional rendering
All content components will check to see if they have any content. If none is received, it will not render.
classNames
prop
Each component has a specific mapping of its BEM elements which you can extend with additional class names. The root element's class can already be extended by simply passing a class
attribute, as with any component. Each key in the object should correspond to a BEM element's name, so if you wish to add a modifier to content__legend
for example:
<template>
<ContentText legend="Sweet potaters" class-names="{ legend: '-dark' }" />
</template>
Each component's BEM element map is detailed below.
Accepted values are:
- a string
- an object
- an array of strings, objects and/or arrays
Repeating elements (such as columns) may also accept a function to which the index
is passed. The function should return one of the above values.
These elements are indicated in the BEM element maps below with parentheses ()
.
The following example adds -dark
to all columns and applies -shift
to columns whose index
are odd numbers.
<template>
<ContentText class-names="{ column: shiftOddColumns }" />
</template>
<script>
export default {
methods: {
shiftOddColumns(index) {
return ['-dark', { '-shift': index % 2 }]
}
}
}
</script>
ContentText
Display text content in a simple layout with optional columns.
Props
| Prop | Type | Default | Description |
| ------- | ------- | ------- | ----------------------------------------------------------------------------------------- |
| center | Boolean | false
| If the entire content should be centered. |
| title | String | null
| The content's main title. |
| legend | String | null
| The content's legend, usually smaller all-caps text that indicates the section's purpose. |
| content | Array | []
| An array of strings representing columns of content. HTML is accepted. |
Slots
default
The default slot is in the footer, below the content columns. Ideal for adding a link.
Details
classNames
map
{ legend, title, row, column(), footer }
column
receives the index
of the column.
Render structure
section.content.-text[.-center]
h2.content__legend
h3.content__title
div.content__row
v-for(index => RawHtml.content__column(index))
footer.content__footer
slot#default
Usage
<template>
<ContentText v-bind="$page.section" class="section" class-names="{ column: shiftOddColumns }" centered>
<RouterLink :to="{ name: 'home' }">Go Home</RouterLink>
</ContentText>
</template>
<script>
import { ContentText } from '@boite-beet/ui-kit'
export default {
components: { ContentText },
data() {
return {
$page: {
section: { title: '#', legend: '//', content: ['<p>lorem</p>', 'ipsum'] }
}
}
},
methods: {
shiftOddColumns(index) {
return ['-dark', { '-shift': index % 2 }]
}
}
}
</script>
ContentSplit
Display text content along side any other type of content, an image by default.
Props
| Prop | Type | Default | Description |
| ------- | ------- | ------- | --------------------------------------------------------------------------------------- |
| reverse | Boolean | false
| If the split content row should be reversed, placing the media on the right side. |
| title | String | null
| The content's main title. |
| legend | String | null
| The content's legend, often smaller all-caps text that indicates the section's purpose. |
| content | String | null
| A string of content. HTML is accepted. |
| image | String | null
| The URL of the image if no media
slot is provided. |
Slots
default
The default slot is below the text content. Ideal for adding a link.
media
The media slot is on the left side unless reverse
is also passed. If no media
slot is provided, falls back to an image.
Details
classNames
map
{ legend, title, row, column(), footer }
column
receives 0
if it is the media and 1
if it is the content.
Render structure
section.content.-split[.-reverse]
div.content__column(0)
slot#media || figure.content__image
div.content__column(1)
h2.content__legend
h3.content__title
RawHtml
slot#default
Usage
<template>
<!-- basic image -->
<ContentSplit
v-bind="$page.section"
image="/images/happy-udy.png"
class="home__about"
class-names="{ legend: '-dark' }"
/>
<!-- custom media -->
<ContentSplit
v-bind="$page.section"
class="home__slider"
class-names="{ column: isText => ({ '-pad-top': isText }) }"
reverse>
<template v-slot:media>
<Slider />
</template>
<RouterLink :to="{ name: 'home' }">Go Home</RouterLink>
</template>
<script>
import { ContentSplit } from '@boite-beet/ui-kit'
import { Slider } from '@/components'
export default {
components: { ContentSplit, Slider },
data() {
return {
$page: {
section: { title: '#', legend: '//', content: '<p>lorem ipsum</p>' }
}
}
}
}
</script>
Helpers
camelKebab
Convert kebab-case to camel-case and vice-versa. Has two usage patterns: methods or getters.
Usage
import { camelKebab } from '@boite-beet/ui-kit'
// methods
camelKebab.toCamel('holy-moly') // holyMoly
camelKebab.toKebab('sweetBabyJesus') // sweet-baby-jesus
// getters
const eatMe = camelKebab('eat-me')
eatMe.camel // 'eatMe'
eatMe.kebab // 'eat-me'
formatInline
Format wrapping characters in text as tags, sort of like custom markdown... sort of. You can create your own formatters using the create
method or use one of the predefined methods.
Note: Only supports single character matching, meaning you cannot replace **sup**
with a tag for example.
Usage
To create your own formatter:
import { formatInline } from '@boite-beet/ui-kit'
// pass the character to replace as the first argument and the tag to use as second
const formatHash = formatInline.create('#', 'strong')
formatHash('I miss you #so much#') // 'I miss you <strong>so much</strong>'
// you can also override the tag if need be
formatHash('Always thinking of #you#', 'em') // 'Always thinking of <em>you</em>'
The predefined methods are:
bold
,star
,strong
— Renders*
as<strong>
italic
,em
,lodash
,underscore
— Renders_
as<em>
strike
,strikethrough
— Renders~
as<s>
The predefined methods work in the same way as your own. If you want to match the same character but render a different tag, pass the tag as the second argument, like so:
import { formatInline } from '@boite-beet/ui-kit'
formatInline.lodash('Trouble in _paradise_', 'u') // 'Trouble in <u>paradise</u>'
keywordPropValidator
Create a Vue prop validator for String
type props that checks the value against a list of strings you provide.
Usage
import { keywordPropValidator } from '@boite-beet/ui-kit'
export default {
props: {
position: {
type: String,
default: null,
validator: keywordPropValidator('top', 'right', 'bottom', 'left')
}
}
}
check (type validation)
Available checks are:
isString
isNumber
(NaN
will returnfalse
)isBoolean
isFunction
isArray
isObject
isNullOrUndefined
isRegExp
isEmpty
(An empty string, array or object as well asnull
andundefined
returntrue
)
Usage
All checks can be used as methods or getters, for example:
import { check } from '@boite-beet/ui-kit'
// methods
const someFunc = () => null
check.isFunction(someFunc) // true
checks.isEmpty({}) // true
checks.isEmpty('') // true
checks.isEmpty(0) // false
// getters
const checkVal = check([])
checkVal.isString // false
checkVal.isArray // true
checkVal.isEmpty // true
Development
Compilation/build is done using Rollup.js.
Installation
Clone the package and install the dependencies.
git clone [email protected]:boitebeet/beet-ui-kit.git
cd beet-ui-kit
yarn
Scripts
build
- compiles the package into thedist/
directory.start
- compiles the package every time the source changes.
Using local package in a project
Yarn has built in functionality that allows you to link a local package to a project. The "package" is the library that will be installed via npm and the "project" is the app/website in which you want to test it.
In the package's directory
yarn link
This only needs to be done once, the link remains available until you run yarn unlink
. There is no danger or issue created by leaving the link permanently.
In your project's directory
yarn link @boite-beet/ui-kit
Now, if you run yarn start
in the package and yarn start
in your project, your project's development server will detect when the package has changed (compile on save) and reload.
Unlinking from your project
Once you are done working on the package and have published, it is recommended to unlink it from your project and reinstall the dependancy to verify that everything has deployed correctly.
To do this, run these commands in your project:
yarn unlink @boite-beet/ui-kit
yarn add @boite-beet/ui-kit@latest
Publishing
This requires you to be a member of the boite-beet org on npm and for you to be logged in to your npm account (using the npm login
command).
Publish from master
Make sure you are in the master
branch and that all your changes have been merged.
git checkout master
git merge develop
Update the version
Run the yarn version
command and enter the new version as prompted. This will also create a version tag in git. Versions should follow the semver pattern of major.minor.patch
- Patch: bugfixes and inconsequential changes
- Minor: new features
- Major: breaking changes
Push changes including version tags
git push --follow-tags
Publish to npm
npm publish
Merge the new package version into develop
git checkout develop
git merge master