Reusable components and directives for Vue. Designed for Vuehaus, but most components work in regular Vue apps.
fh-components
saves you the trouble of rewriting common components like images, video players, svgs, and several other elements. If you're looking at writing a custom component in Vuehaus, check if one already exists here first - and if it doesn't, feel free to contribute!
npm install fh-components --save
When you register components:
import ComponentName from 'fh-components/component-name'
// Global in main.js...
Vue.component('component-name', ComponentName)
// ...or local to a .vue file...
export default {
components: {
'component-name': ComponentName
}
}
// ...or a global component one-liner (no `import` statement needed)
Vue.component('component-name', require('fh-components/component-name'))
<a-div :href="myString">Link text</a-div>
Safe way to render internal, external, or missing links.
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
✅ | ❌ | ✅ |
Props
Name | Type | Default | Description |
---|---|---|---|
force-new |
Boolean |
false |
Whether the link should always open in a new window. |
href |
String |
empty | URL to link to. Handles same-origin URLs with router-link , external URLs with a , and falsey values with the replace-with tag. |
new-window |
Boolean |
true |
If component renders as an <a> tag, open in a new window. |
replace-with |
String |
'div' |
If the href isn't a link, render contents inside this tag instead. |
Classes
Name | Conditions | Notes |
---|---|---|
anchor-div |
Always | |
a-${ replace-with } |
Always | Substitutes with the replace-with tag name. |
Notes
- Renders content in a
<router-to>
tag ifhref
starts with/
or includes the samelocation.origin
. - Renders content in an
<a>
tag ifhref
is a truthy value not starting with/
. - Renders content in a given tag (
replace-with
, defaultdiv
) ifhref
is falsey.
<count-up :to="1000" :duration="1000" ref="count"/>
<button @click="$refs.count.running = true">Start Counting</button>
Count up to a specified number over a period of time. Requires Popmotion (npm install popmotion
if this component throws errors).
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
✅ | ❌ | ✅ |
Props
Name | Type | Default | Description |
---|---|---|---|
appear |
Boolean |
false |
Whether or not to start counting on mounted . |
delay |
Number |
0 |
How long to delay before starting the count. |
duration |
Number |
700 |
How long, in ms, to count up to to . |
from |
[String, Number] |
0 |
Number to start at. |
to |
[String, Number] |
100 |
Number to count up to. |
Classes
Name | Conditions | Notes |
---|---|---|
count-up |
Always |
Notes
- Set
running
totrue
to start counting.running
will automatically be set back tofalse
when complete.
<hamburger-button/>
<hamburger-button>
<svg-image src="custom-hamburger.svg" />
</hamburger-button>
Lightweight hamburger SVG with open and closed states. Requires Vuehaus.
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
❌ | ✅ | ✅ |
Classes
Name | Conditions | Notes |
---|---|---|
hamburger |
Always | |
activated |
when $store.state.menuOpened is true |
Notes
- Calls Vuehaus's
'OPEN_MENU'
or'CLOSE_MENU'
when clicked, depending on currentactivated
state. Will also update correctly when menu is opened or closed elsewhere. - Default slot is a three-line hamburger icon that changes to an "X" when activated. The user can replace this with their own svg using the
svg-image
component.
<image-loader :images="arrayOfImagesToLoad"/>
This component will, given a list of serialized images, preload those images. Will handle Vuehaus image objects or image URL strings. All images are set to display: none
.
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
✅ | ✅ | ✅ |
Props
Name | Type | Default | Description |
---|---|---|---|
images |
Array |
[] |
Array of serialized images or URL strings to load. |
size |
String | full |
WordPress name for image size to load. |
<load-on-view
:url="urlToFetchWhenInView"
@data="handleAnyData"
@text="handleTextResponse"
@json="handleJsonResponse"
@error="handleError"/>
This component will keep track of its own scroll position, and when it enters the viewport will send a fetch request to a provided URL, emitting any resulting error or data through events. This is most useful for "infinite-scroll" functionality, where you feed pagination URLs into this component and use the resulting request data.
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
✅ | ❌ | ❌ |
Props
Name | Type | Default | Description |
---|---|---|---|
url |
String | '' |
Target URL to send a fetch request to once the component is in view. |
repeatUrl |
Boolean | false |
If set to false, load-on-view will not send the next request until the provided URL changes. |
fetchConfig |
Object | { credentials: 'same-origin' } |
An configuration object to be used with the fetch request. |
Events
Name | Signature | Description |
---|---|---|
data |
(response) | Fired whenever a fetch request is completed and returns a 2xx status code |
error |
(err) | Fired whenever an error occurs during the fetch request, or when the request returns a non-2xx status |
on-text |
(textData) | Fired when a fetch is completed and the contentType of the response is text |
on-json |
(jsonData) | Fired when a fetch is completed and the contentType of the response is json |
on-buffer |
(arrayBuffer) | Fired when a fetch is completed and the contentType of the response is neither text nor json |
Classes
Name | Conditions | Notes |
---|---|---|
load-on-view |
Always |
<mailing-list
submit-text="Sign Up"
placeholder="Your email address"
:action-url="actionUrl"
:token="token"/>
Flexible newsletter signup component. Supports Mailchimp and Madmimi.
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
✅ | ❌ | ✅ |
Props
Name | Type | Default | Description |
---|---|---|---|
provider |
String | mailchimp |
Newsletter service provider. Either mailchimp or madmimi . |
actionUrl |
String | none (required) | Action URL for signup form. |
token |
String | none (required) | API token. |
successMessage |
String | Thank You! |
Message to show to user on successful signup |
submitText |
String | Subscribe |
Text on 'submit signup' button |
placeholder |
String | Email Address |
Placeholder text for email address input |
transitionName |
String | newsletter-submit |
Name for transition wrapping mailing-list element |
delay |
Number | 5000 | Time in ms to wait before firing cookieCreated event |
cookieLength |
Number | 30 | Time in days until cookie expires |
Slots
Name | Location |
---|---|
top |
First element in mailing list wrapper, before any inputs |
label |
Label for email input element. Default: <label for="mailing_list_email">Email</label> |
before-form |
Wrapped by transition, appears before input form (hidden after form submitted successfully) |
after-form |
Wrapped by transition, appears after input form (hidden after form submitted successfully) |
success |
Appears after successful submission and successMessage prop |
error |
Appears after form if error detected. Not wrapper by transition element. |
bottom |
Last element in mailing list wrapper. After all inputs and transition wrap. |
Events
Name | Signature | Description |
---|---|---|
addressSubmitted |
{ success: Boolean, errorMessage: String } |
Fires when an email address is submitted |
cookieCreated |
null | Fired when a cookie is created. (Designed to trigger overlay activation) |
Classes
Name | Conditions | Notes |
---|---|---|
fh-mailing-list |
Always | |
loading |
If mailing list request has been submitted | |
state-${ state } |
Always | Either state-success , state-error , or state-none , depending on submission state |
provider-${ provider } |
Always | Either provider-mailchimp or provider-madmimi |
empty |
If the email input field is empty |
Notes
- Element to build out common mailing list signups.
- Requires an action URL and an API token.
- Mailchimp: The action URL is the value of the
action
attribute on theform
element in the form builder, and the API token is the value of thename
attribute on the input wrapped by theleft: -5000px
div.
- Mailchimp: The action URL is the value of the
- Waits
delay
ms, then fires thecookieCreated
event and creates a cookie that prevents firing that event again forcookieLength
days.
<responsive-image :src="sourceUrl" :object="serializedRestEasyObject"/>
Creates and fades in an image. Adds a placeholder for the image with a given background-color to prevent content jumping when the image loads.
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
✅ | ✅ | ❌ |
Props
Name | Type | Default | Description |
---|---|---|---|
aspect |
[String, Number] | '' |
Aspect ratio of desired image, as a percentage. aspect="56.25" would evaluate to a 56.25% aspect ratio. Calculated from height and width if not explicitly stated. |
color |
String | 'transparent' |
Background placeholder color. Any CSS-compatible color is valid. |
fill-space |
Boolean | false |
When true , position absolutely and force the image to take up all available space; when false , use the image's natural aspect ratio. |
fit |
String | 'cover' |
Object-fit value for the image - cover or contain . |
height |
[String, Number] | '' |
Natural image height in pixels. |
html |
String | '' |
Desired element as an HTML string. Provides all the sizing, fade-in, etc. benefits of a regular responsive-image. |
object |
Object | {} |
Serialized Rest-Easy attachment. Fills out the rest of these props automatically except fit . |
poster |
String | '' |
URL to poster for video. If false , no poster used; if blank, defaults to the parsed image source. |
respect-max |
Boolean | false |
Whether the image will have a max-width and max-height based on its natural dimensions. |
size |
String | 'full' |
WordPress image size. |
src |
String | '' |
Same functionality as <img src="..."> . |
video-src |
String | '' |
An optional video URL if the component should display a looping video rather than an image. Set to null to disable the video option. |
volume |
Number | 0 |
Controls the volume when rendering a video. If set to 0 (or not set) the video will be muted and will be able to autoplay. |
width |
[String, Number] | '' |
Natural image width in pixels. |
Slots
Name | Location |
---|---|
Default | Content inside of image-padding, after image. |
Classes
Name | Conditions | Notes |
---|---|---|
fill-space |
if fill-space prop is set to true |
|
fit-${fit} |
Always | Either fit-cover or fit-contain , depending on the fit prop. |
has-video |
if videoSrc is truthy |
|
loading |
Image hasn't finished loading | |
responsive-image |
Always | |
rsp-image-module |
Always |
Notes
- If you add a link to an .mp4 video in the "Alt" field in WordPress, this element will create and render a video, using the provided image as the poster (see "Poster" under "Attributes" here).
- Also works when
object
is set to an Advanced Custom Fields image array. Only providesfullscreen
size and image alt in this case. - Works with Focushaus, if installed, to set a custom focal point for an image. Defaults to standard browser behavior, focal point in the center of the image.
<reveal-footer>
<footer>I'm the footer content.</footer>
</reveal-footer>
A component that reveals the section at the bottom of a page. Like the footer on the Funkhaus site.
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
✅ | ❌ | ❌ |
Slots
Name | Location |
---|---|
Default |
Classes
Name | Conditions | Notes |
---|---|---|
reveal-footer |
Always | |
footer-wrap |
Always |
Notes
- Adds a slot wrapped with an empty div
footer-wrap
, preceded by an empty div that expands to match thefooter-wrap
height.footer-wrap
isposition: fixed
and hasright
,bottom
, andleft
set to 0.
<scroll-to target=".scroll-target">Scroll to Target</scroll-to>
Wrap this component around any element to add "scroll-to" functionality, where the window is scrolled to a target element when the wrapped element is clicked.
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
✅ | ❌ | ❌ |
Props
Name | Type | Default | Description |
---|---|---|---|
target |
String/Object | scroll-target |
Target element to scroll to. This can be the name of a $ref, a CSS selector, or a DOM element. |
duration |
Number | 1000 |
The duration of the scroll animation in ms. |
easing |
String | inOutQuad |
The animation easing to use. See ease for all valid options. |
offset |
Number | 0 |
Number of pixels to offset the scroll target by. Can be positive or negative. |
debug |
Boolean | false |
Whether or not to display debug information on click. |
Slots
Name | Location |
---|---|
Default | Content to be wrapped by scroll-to button |
Events
Name | Signature | Description |
---|---|---|
complete |
() |
Fires when the page has finished scrolling to the target. Fired immediately if target is not found. |
Classes
Name | Conditions | Notes |
---|---|---|
scroll-to |
Always |
<slide-show :slides="[1, 2, 3, 4]">
<div class="slide" slot="slide" slot-scope="args">
{{ args }}
</div>
</slide-show>
This component creates a basic slideshow element that will cycle through a list of items that you provide.
Requires:
- An array of
slides
that will serve as the source for the individual slides - A
slide
slot describing the format of an individual slide
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
✅ | ❌ | ✅ |
The simplest possible slide-show
looks like this:
<slide-show :slides="[1, 2, 3]">
<div slot="slide" slot-scope="{ slide }">
<!-- This will create a slideshow of the numbers 1, 2, and 3, as specified in the "slides" prop-->
{{ slide }}
</div>
</slide-show>
See the slots section for more information.
Name | Type | Default | Description |
---|---|---|---|
slides |
Array | [] |
Array of items to be iterated over and turned into slides. |
auto |
Boolean | true |
Controls if slideshow should auto-advance. |
swipe |
Boolean | true |
Controls if left/right swipe should be enabled |
loop |
Boolean | true |
If true, slideshow will loop from last item back to first on automatic playthroughs |
manual-loop |
Boolean | false |
If true, slideshow will loop from last item back to first on automatic and manual slide changes |
infinite |
Boolean | true |
If true, slideshow will do a next style transition when looping from last to first and vice versa. |
interval |
Number | 4000 |
Time between auto slide transitions. |
delay |
Number | 500 |
Time to delay before initiating auto slide interval. |
index |
Number/Object | null |
Used to manually control the current active slide of the slideshow. This prop can be bidirectionally bound using the sync modifier - <slide-show :index.sync="myLocalIndex"/> will keep myLocalIndex up to date and reflect updates immediately if changed. Note that the value will also wrap correctly if under 0 or over the total slide number. |
pagination |
Boolean | true |
Controls if pagination elements should be rendered or not. |
localKeyboard |
Boolean | false |
If true, slideshow will bind keyboard listeners to the slideshow DOM element. If false, keyboard events will be bound to window. |
nextKey |
Number | 39 |
Keycode for which key will trigger the next action. Default is the right arrow. |
prevKey |
Number | 37 |
Keycode for which key will trigger the prev action. Default is the left arrow. |
css |
Boolean | true |
Controls if the transition should use CSS classes or not. Use this when overriding the default transition using the provided javascript hooks. |
can-control |
Boolean | true |
Controls whether or not the user has any manual control over this slideshow progression. |
force-transition |
String | '' |
Tell the slideshow to use this transition name instead of the default next and prev classes. Useful if the transition will not change depending on slideshow direction. |
slide-wrap |
String | div |
The element to wrap each individual slide in. Required to use the transition correctly. |
enter |
Function(element, done, direction) | null |
The enter function for the JS transition. Only runs if css is set to false . done is a function that must be called when transition completes; direction will either be prev or next depending on the slideshow direction. |
leave |
Function(element, done, direction) | null |
The leave function for the JS transition. Only runs if css is set to false . done is a function that must be called when transition completes; direction will either be prev or next depending on the slideshow direction. |
startingSlide |
Number | 0 |
Zero-based index of starting slide. |
fh-slideshow
transitioning
: Active when slideshow is mid transitionfirst-slide
: Active when the current index is the first slidelast-slide
: Active when the current index is the last slide
To use a transition other than the built-in one (for example, a fade transition):
<slide-show
force-transition="fade">
...
</slide-show>
In order to use this component, you'll have to utilize the Scoped Slots feature of vue components. This component has 4 available slots, and 2 of them are scoped.
slide: Template for a single slide element
This is the most important slot. It allows you to provide a template to the component telling it how to render each slide item you provided through the slides
prop. The slot accepts 2 arguments, slide
and index
. slide
holds the data of whatever item is being iterated over, and index
is the current item's index. Here's a simple example usage:
<slide-show :slides="pages">
<div class="slide" slot="slide" slot-scope="args">
<responsive-image :object="args.slide.featuredAttachment" />
</div>
</slide-show>
In this example the slot is being fed all provided arguments through the args
object. The single property on the args
object will be slide
, which carries the value of a single page. In this example an image object is being provided to a responsive-image, but you can use any data from the slide object within this slot to render your template.
pagination-item: Template for a single pagination item in the slideshow
This is the second scoped slot, it's used to define a template that the component will use when rendering the pagination. It also supports a single argument, slide
. Building on the last example:
<slide-show :slides="pages">
<!-- ... -->
<span slot="pagination-item" slot-scope="args">{{ args.slide.title }}</span>
</slide-show>
This would render a list of pagination items, each with the title of the page inside. Pagination elements are automatically given event listeners to trigger their corresponding slides when clicked, and the currently active pagination item is given the class active
.
nav-next: Element to be used as the next trigger
This is a non-scoped slot that will render an element to be used as the next trigger when clicked. The component will automatically add the click listeners to this component. Example Usage:
<slide-show :slides="pages">
<!-- ... -->
<svg-image src="arrow-right.svg" slot="nav-next" />
</slide-show>
nav-prev: Element to be used as the prev trigger
This non-scoped slot functions exactly the same as nav-next
, but triggers the previous action.
before: Element(s) to go within the slideshow just before the slide slot
This non-scoped slot allows you to add extra content into the slideshow that does not affect the functionality of the core elements.
default: Element(s) to go within the slideshow, after all other slots.
This non-scoped slot is the default and allows you to add extra content into the slideshow that does not affect the functionality of the core elements.
By default, there is a simple css slide transition being applied to the slideshow. It has the name fh-slide-${ direction }
, with direction being either next
or prev
depending on which way the slideshow is going. For simple transitions it's easy to override the default. For example, if you wanted to take the existing transition but change the speed to 3s you might do this:
.fh-slideshow.fh-slide-next-enter-active,
.fh-slideshow.fh-slide-next-leave-active,
.fh-slideshow.fh-slide-prev-enter-active,
.fh-slideshow.fh-slide-prev-leave-active {
transition-duration: 3s;
}
If css transitions alone are not enough for the effect you want, you can opt to use js hooks instead. The full set of js transition events are available to the main slide-show
component, so you can pass them as props there and use javascript to create your effects. Keep in mind that when doing this you will want to set the css
prop to false so that the transition element is forced into js mode.
Props
elements
: String or Array, defaultspan
. Wrap each resulting line in this element with classesline
andline-{ line number }
. Ifelements
is an array, split-text will wrap each line in its respective element, using the last element for any extra lines.separator
: String or Array, default' – '
. Splits text by this separator. If set to an array, find the first separator that results in a split string and use that one.pieces
: Number or Array, default-1
. Only include these zero-indexed pieces of the split text. If set to -1 (default), include the full split text.text
: String, default empty. Text to split.wrapper
: String, defaultspan
. Element wrapping rendered lines.
Notes
- Splits given text according to a given separator. Useful for formatting titles, for example -
Title - More Title Information
will render as<span>Title</span><span>More Title Information</span>
. - Contains two slots, named
before
andafter
, that render their content before and after the split text.
<sticky-wrap>
<h2>I'm stuck content</h2>
<p>I'll be stuck too</p>
</sticky-wrap>
Mimics position: sticky
using a wrapper element and translation. Sticks an element to the top of the screen without breaking page flow. Place content to be stuck inside the main slot
Standalone | Enhanced by Vuehaus | SSR Capable |
---|---|---|
✅ | ❌ | ❌ |
Props
Name | Type | Default | Description |
---|---|---|---|
wrapper |
String | div |
Element that wraps the entire component. |
margin |
Number/String | 0 |
Distance in pixels to pad the top of the sticky element. |
max |
Number/String | -1 |
The maximum distance this element will be able to translate. |
Classes
Name | Conditions | Notes |
---|---|---|
sticky-wrap |
Always |
Props
wrapper
: String, defaultspan
. Wrapper element for the typed text.html
: String, default''
. HTML to be typed out.minTime
: Number, default2
. Minimum number of ms to pass between keystrokes.maxtime
: Number, default5
. Maximum number of ms to pass between keystrokes.
Classes
text-typer
Notes
- Element to create a text typing effect that preserves HTML tags.
- Uses a naive tag splitter that just detects for the presence of
<
or>
characters.
Classes
- Transition classes applied to a transition called
fade
.
Notes
- Fades the contained element over 0.4 seconds.
Notes
- Velocity.js match of jQuery's slideToggle method, wrapped in a Vue transition.
Props
reset
: Boolean, defaulttrue
. Whether or not the changed elements should have their inline styles reset after the animation complete.sequence
: Array, default[]
. Sequence to run on appear.wrapper
: String, defaultdiv
. Element in which to wrap the enclosed content.
Classes
velocity-animate
Notes
- A component to ease creating multi-step Velocity transitions. The
sequence
refers to a Velocity sequence, first introduced in the UI pack. Instead of raw elements, thee
property should contain the ref of the desired element. For example:
<template>
<velocity-animate :sequence="sequence">
<h2 ref="title">I'm here!</h2>
<h3 ref="subtitle">So am I!</h3>
<p ref="body">I'm the body</p>
</velocity-animate>
</template>
<script>
export default {
computed: {
sequence(){
return [
{ e: 'title', p: { opacity: [1, 0] } },
{ e: 'subtitle', p: { opacity: [1, 0] } },
{ e: 'body', p: { translateX: [0, '-100%'] }, o: { sequenceQueue: false } }
]
}
}
}
</script>
This will make the h2 fade in, then the h3 fade in and p tag slide in from the left.
velocity-animate
also allows you to set initial states of elements by forcefeeding starting values. See this section of the Velocity docs for details. Instead of just animating from the second value to the first, velocity-animate sets the element's desired property to the second value from the start of the animation.
Props
src
: String, default''
. The source of the video to embed. This can either be a full iframe code, or for Vimeo videos can just be a permalink to the video.autoplay
: Boolean, defaulttrue
. Controls whether the component should attempt to autoplay the video. Only vimeo videos support autoplay at the moment.autoOffsets
: Boolean, defaulttrue
. If set to true, the components will attempt to automatically calculate a fitToParent offset based on the dimensions of any slot elements provided.paused
: Boolean, defaultfalse
. If set to true, the player will pause; if false, the player will play.muted
: Boolean, defaultfalse
. If set to true, Vimeo player will start muted.is-responsive
: Boolean, defaultfalse
. If set to true the video will behave like fitVids, pushing down content on the page according to its natural aspect ratio rather than filling the available space.
Slots
before
: Renders directly before the element that holds the iframe.after
: Renders directly after the element that holds the iframe.
Notes
- This component uses
fitToParent
to automatically scale and center the iframe within the dimensions of the nearest positioned parent element. Example usage:<video-stage src="https://vimeo.com/123456789" @ended="goToNextPage" />
.
Events
- Events are only supported for Vimeo-based videos.
ended
: fires once the video has reached the end.play
: fires any time the video begins playbackpause
: fires any time the video is pausedwidth
: fires any time the video is resized by fitToParent. Value is the new width in pixels.height
: fires any time the video is resized by fitToParent. Value is the new height in pixels.error
: fires if there is an error during calculation. Value will be the error object that was triggered.
Props
-
html
: String, default''
. HTML that can function as a Vue template. The component will dynamically compile the contents, and inject the results into the vue component tree. -
unwrap
: Array, default['p > img', 'p > iframe']
. Array of CSS selectors describing elements to unwrap from their parent nodes. (WordPress automatically wraps newlines in<p>
tags, so this is a useful feature to remove standalone media from those<p>
tags.) -
fitvids
: Boolean, defaulttrue
. When set to true, the component will run fitvids on all content inside. -
replace
: Array, default[]
. Array of objects containing:selector
- String with CSS selector for elements to replacecallback
- Function describing what to replace old element with. Accepts one argument referring to the old element; should return a String or Node with the element's replacement.
This feature lets you replace items in
wp-content
. For example:<wp-content :html="contentHtml" :replace="[ { selector: 'img', callback: el => `<p>Image with source ${ el.src }</p>` } ]"
This setting would replace all
img
tags with ap
containing the text 'Image with source [element's source]'. Useful for modifying wp-content HTML before it is rendered on the DOM.
Classes
wp-content-placeholder
: only present when no html template is providedwp-content-rendered
: only present when an html template was provided and successfully rendered
Notes
- This was built to allow components to be used with the the contents of a Wordpress post. It's very effective for that purpose, but more generally can be used to compile any dynamically loaded template and render it into the component tree. Be aware that this should only ever be used when the template going into the
html
prop is trusted.
Vuehaus-specific component to build WordPress menus. Uses the wp-menu-item
component internally.
Name | Type | Default | Description |
---|---|---|---|
slug |
String | '' |
The WordPress slug of the menu to use. |
name |
String | 'Main Menu' |
The WordPress name of the menu to use. |
forceRouterLink |
Boolean | false | Whether or not this wp-menu should force the use of router-link s instead of a tags. |
menu
${ slug }
: Either theslug
prop or a kebab-cased version of thename
prop, whichever is available.
This component comes with an optional scoped slot that will allow you to override the default markup for a single menu item.
menu-item: Template for single menu item within component
This slot accepts 2 arguments, menuItem
, which carries the serialized object of the single item that is being iterated over, and index
, which carries the index of the current menu item. Here is an example of how you might use it to add an arrow icon next to the name of each element in the menu:
<wp-menu name="Main Menu">
<li slot="menu-item" slot-scope="{ menuItem, index }">
<router-link :to="menuItem.relativePath">
<svg-image src="arrow.svg" />
<span>{{ index }}. </span>
<span v-html="menuItem.title" />
</router-link>
</li>
</wp-menu>
Directives are declared as attributes. Remember to prefix v-
to the directive name (so full-height
becomes v-full-height
)!
To use fh-components directives in your Vue app:
import directiveName from 'fh-components/v-directive-name'
Vue.directive('directive-name', directiveName)
To pass arguments to directives:
<my-component v-my-directive="{ key: 'value' }"/>
Directives may also have "modifiers" to go with them. Modifiers can be used like this:
<my-component v-my-directive.example />
Notes:
- Applies a class to an element on hover or focus. Only removes that class when an animation is finished with its current iteration. See this pen for an example - the arrows will always finish their bounce animations.
- Must be more than 1 animation iteration to remove class correctly.
- Accepts one argument, the class name to apply. Default
animating
. - Recommended use:
<template>
<router-link
v-animated
class="link"
to="/">I'll animate!</router-link>
</template>
<style lang="scss">
.link.animating {
// must be more than one iteration - this won't work:
animation: my-animation 1s 1;
// but this will:
animation: my-animation 1s infinite;
}
@keyframes my-animation {
// your keyframes...
}
</style>
Not supported! Note that the v-coverup
directive is very buggy on Safari and Firefox and probably won't be fixed any time soon.
Notes:
- Sticks the bottom edge of an element to the bottom edge of the screen. Like position: sticky, but keeps the element stuck.
- Jittery in Firefox and Safari. Works in Chrome.
Notes
- Gives the user access to mousedowm/move/up events and deltas.
- User needs to full functionality in arguments - directive just handles communicating data to those events.
Arguments
dragStart
: Function, default null. Called when the user clicks on an element. Takes two arguments, the event itself and an object with the clientX and clientY properties inx
andy
.drag
: Function, default null. Called when the user clicks on an element. Takes two arguments, the event itself and an object with:lastPos
: the clientX and clientY properties inx
andy
delta
: the change in pixels between this frame and the previous inx
andy
.totalDelta
: the change in pixels of the drag from start to the current frame, inx
andy
.
dragStop
: Function, default null. Called when the user releases a click on an element. Takes two arguments, the event itself and an object with:lastPos
: the clientX and clientY properties inx
andy
totalDelta
: the change in pixels of the drag from start to finish, inx
andy
.
Notes:
- Sizes the element to 100% of the window height. Replacement for the occasionally buggy 100vh css value.
Modifiers
min
: If provided, directive will use themin-height
css property rather thanheight
.
Arguments
above
: String, defaultabove-view
. Class when element is above the viewport.below
: String, defaultbelow-view
. Class when element is below the viewport.left
: String, defaultleft-of-view
. Class when element is left of viewport.right
: String, defaultright-of-view
. Class when element is right of viewport.inView
: String, defaultin-view
. Class when element is within the viewport.onEnter
: Function, defaultnull
. Method to call when an element enters view from anywhere. Accepts one argument, the element that has entered the viewport.onEnterAbove
: Function, defaultnull
. Method to call when an element enters view from the top of the screen. Accepts one argument, the element that has entered the viewport.onEnterBelow
: Function, defaultnull
. Method to call when an element enters view from below the screen. Accepts one argument, the element that has entered the viewport.onEnterLeft
: Function, defaultnull
. Method to call when an element enters view from left of the screen. Accepts one argument, the element that has entered the viewport.onEnterRight
: Function, defaultnull
. Method to call when an element enters view from right of the screen. Accepts one argument, the element that has entered the viewport.onLeave
: Function, defaultnull
. Method to call when an element leaves view from anywhere. Accepts one argument, the element that has ;eft the viewport.onLeaveAbove
: Function, defaultnull
. Method to call when an element leaves view, going above the screen. Accepts one argument, the element that has left the viewport.onLeaveBelow
: Function, defaultnull
. Method to call when an element leaves view, going below the screen. Accepts one argument, the element that has left the viewport.onLeaveLeft
: Function, defaultnull
. Method to call when an element leaves view, going left of the screen. Accepts one argument, the element that has left the viewport.onLeaveRight
: Function, defaultnull
. Method to call when an element leaves view, going right of screen. Accepts one argument, the element that has left the viewport.
Modifiers
appear
: If set, events and classes will be calculated on insertion.
Notes
- Applies classes to an element based on its viewport visibility.
- Can also fire callbacks when an element has entered or left the viewport.
Arguments
You can pass either a function or an object to v-interact
. If you pass a function, that function will act as the callback
parameter below.
If you pass an object, your available parameters are:
callback
: Function, default() => {}
. Callback to fire when any event triggers.events
: Array of objects, default{ mouseenter: callback, focus: callback }
or{ mouseleave: callback, blur: callback }
, depending on whether theend
modifier exists (see below). The keys in this object are event types, the values are the functions to call on those events.
Modifiers
end
- Consolidatesmouseleave
andblur
instead ofmouseenter
andfocus
.
Notes
-
Event consolidator that handles both
mouseenter
/focus
andmouseleave
/blur
. -
v-interact
funnels multiple events into one callback and/or event. It's designed to consolidate mouseenter/focus and mouseleave/blur, but it can be used with any set of events. -
Example:
<!-- method defined in Vue instance - receives event as argument --> <router-link v-interact="startInteract"> <!-- method defined in Vue instance with additional argument --> <router-link v-for="(link, i) in links) v-interact="evt => startInteract(evt, i)"> <!-- mouseleave/blur event --> <router-link v-interact.end="endInteract"> <!-- custom events --> <router-link v-interact="{ events: { mousedown: clicked, mouseup: clicked } }">
Modifiers
esc
- Only run callback onesc
keydown.up
- Only run callback onup arrow
keydown.right
- Only run callback onright arrow
keydown.down
- Only run callback ondown arrow
keydown.left
- Only run callback onleft arrow
keydown.
Notes
Bind a global keydown listener to this component. Listener is created and destroyed with the component. For example, to implement an esc key toggle on a menu:
<menu-component v-keydown.esc="() => $store.commit('TOGGLE_MENU')"/>
Note that the value must be an arrow function or a reference to a method in the Vue instance. These should work:
<menu-component v-keydown.esc="toggleMenu"/>
<menu-component v-keydown.esc="() => toggleMenu(false)"/>
But this won't:
<menu-component v-keydown.esc="toggleMenu()"/>
Arguments
containerActivatedClass
: String, defaultrh-active-within
. When a triggering component is hovered over or focused, the target element will have this class added to it.elActivatedClass
: String, defaultrh-active
. A triggering component that is hovered over or focused will have this class added to it.processedClass
: String, defaultrh-processed
. Triggering components will have this class added to them. (Usually left alone - mainly for internal directive use.)selectors
: Array or String, default['a']
. All children of the target element that match this selector will become triggering components, reverse-hover listeners attached.
Notes
- Allows for a "reverse hover" effect, where hovering over one element of a set affects all elements of a set. For example:
<ul class="container" v-reverse-hover>
<li v-for="url in links">
<a class="link" :href="url">Link to {{ url }}</a>
</li>
</ul>
.container.rh-active-within {
background-color: #ccc;
}
.rh-active-within .link:not(.rh-active) {
opacity: 0.5;
}
.link.rh-active {
font-weight: 700;
}
When hovering/focusing over any .link
element, the .container
will receive the rh-active-within
class (giving it a gray background) and all non-hovered/focused .link
s will be reduced to 0.5 opacity. The element that triggered the reverse-hover (the original hovered/focused .link
) will have its font-weight set to 700.
- You can set the class names yourself by passing an object:
<ul v-reverse-hover="{ containerActivatedClass: 'container-activated' /* etc... */ }">
- You can also choose which selectors will act as triggers:
<ul v-reverse-hover="{ selectors: ['a', 'button'] /* <a> and <button> tags will trigger the reverse-hover */ }">
To use an fh-component mixin:
import mixin from 'fh-components/mixins/mixin-name'
export default {
mixins: [mixin]
}
Mixins define component properties, lifecycle hooks, data, and more to save you time when writing new components.
Name | Type | Description |
---|---|---|
idle |
Boolean | Whether or not the component is idle. |
idleDelay |
Number | How long, in ms, to wait before marking as idle |
Fires an event after a given number of ms. For example:
<template>
<img src="img.png" v-show="idle">
</template>
<script>
import idle from 'fh-components/mixins/idle.js'
export default {
mixins: [idle]
}
</script>
This will hide the image after 5 seconds of inactivity and show it again when the user scrolls or moves the mouse.
A very easy-to-use infinite scroll mixin for Vuehaus.
Name | Type | Description |
---|---|---|
nextUrl |
string | URL containing next batch of posts. |
grid |
Array | Array of Rest-Easy serialized posts, representing the full list of posts to display. |
Requires load-on-view
(see above). Use like this:
<template>
<main class="archive">
<div class="post-grid">
<div
class="post-block"
v-for="(post, i) in grid"
:key="i">
<!-- Your post-block templating here - eg: -->
<h2 v-html="post.title"/>
</div>
<load-on-view
:url="nextUrl"
@data="loadNext"/>
</div>
</main>
</template>
<script>
import infiniteScroll from 'fh-components/mixins/infiniteScroll'
export default {
mixins: [infiniteScroll]
}
</script>
nextUrl
will update as long as there are more pages to display, and grid
will stay populated with all posts loaded so far.
Name | Type | Description |
---|---|---|
clientRect |
Object | Object describing element dimensions. |
setRect |
Function | Update clientRect . Called internally. Can be called manually to force an update. |
rectThrottle |
Number | ms throttle before firing scroll and resize events. Default 150. |
Like Masonry but more lightweight and based on CSS grid.
Name | Type | Description |
---|---|---|
transforms |
Array of strings | Array of pixel lengths to offset each packed block. |
containerHeight |
String | Height of the container element in pixels. |
pack |
Function | Recalculate the packing values. Requires one argument, the container element. |
- Best used with waitFor so that content is rendered before sizes are calculated.
- Set each block to translate up by its corresponding
transforms
value to create a Masonry-style, tightly-packed grid. - It's a good idea to watch the window width in a debounce function and rerun
pack
when it changes. - Known bugs:
- Sometimes, child content wrapped in a flexbox won't calculate height correctly. To solve, wrap the flexbox in a container div.
- Sometimes when watching the
window.innerWidth
directly, the sizes and transforms don't calculate correctly. To solve, debounce or throttle the window width watcher.
Example:
<template>
<section :style="{ height: containerHeight }" ref="container">
<repeated-block
v-for="(block, i) in ..."
:key="i"
:style="{transform: transforms[i]}"/>
</section>
</template>
<script>
import waitFor from 'fh-components/mixins/waitFor'
import pack from 'fh-components/mixins/pack'
export default {
mixins: [waitFor, pack],
mounted() {
// pack when the grid is ready to render
// (this.pack will receive this.$refs.container from waitFor)
this.waitFor('$refs.container', this.pack)
},
watch: {
// assuming a debounced/throttled window width value on the root element
'$root.winWidth'() {
// pack when the window width changes
this.pack(this.$refs.container)
}
}
}
</script>
<style>
/* The container element must used display: grid! */
/* Other than that, row/column gaps, templates, and breakpoints will work correctly */
.container {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-column-gap: 80px;
}
@media 'only screen and (min-width: 1800px)' {
.container {
grid-template-columns: repeat(4, 1fr);
}
}
@media 'only screen and (max-width: 750px)' {
.container {
grid-template-columns: repeat(1, 1fr);
}
}
</style>
Increment a number over a time period. Useful for sequential fading on work grids, for example.
Name | Type | Description |
---|---|---|
sequenceStep |
Number, default 0 | Current step of the sequence. |
sequenceMax |
Number, default 100 | Maximum number for the sequence to count to. |
sequenceStepLength |
Number, default 300 | ms between each sequenceStep increment |
sequenceInitialDelay |
Number, default 0 | ms to wait before starting the count-up |
incrementSequenceStep |
Function | Increment sequenceStep by 1. If sequenceStep is less than sequenceMax , set a timeout to call this function again in sequenceStepLength ms. |
reset |
Function | Resets sequenceStep to 0 and starts counting up again after sequenceInitialDelay ms. |
A quick and simple mixin for keeping track of the bounding box (width
, height
, top
, and left
, in screen-space px) of an element. Updated automatically on resize and scroll.
Name | Type | Description |
---|---|---|
awaitedItem |
String, default null | Item to wait for. |
awaitedItemCallback |
Function, default null | Callback to run when awaitedItem exists. |
awaitedItemCallbackCompleted |
Boolean, default false |
Whether or not the callback has run. Set internally. |
waitFor |
Function (string, callback) | Wait for the first argument to exist, and when it does, run callback . callback receives the awaited item as its only parameter. |
Checks for the existence (using lodash/get
) of an item. If the item exists, runs a callback immediately; if it doesn't, watches the current loading state and runs the callback when the page is loaded.
Useful for manipulating data that may or may not be asynchronous. For example, to select all a
tags in a content field when they've been loaded and rendered:
<template>
<wp-content :html="content" ref="content"/>
</template>
<script>
import waitFor from 'fh-components/mixins/waitFor'
export default {
mixins: [waitFor],
mounted(){
this.waitFor('$refs.content.$el', this.selectLinks)
},
methods: {
selectLinks(el){
// `el` = this.$refs.content.$el
console.log([...el.querySelectorAll('a')])
}
}
}
</script>
Plugins are outside the scope of fh-components, but here's a list of Funkhaus-maintained Vue plugins:
- Can-Vue, Vue + reactive HTML5 Canvas
fh-components comes with built-in Popmotion animations.
Fade in a series of items using Popmotion's stagger. Example:
<template>
<ul ref="list">
<li>Item one</li>
<li>Item two</li>
<li>Item three</li>
</ul>
</template>
<script>
import { sequentialFade } from 'fh-components/animations/sequential-fade'
export default {
mounted(){
// Pass an array of nodes to the function to fade in sequentially.
// using defaults:
sequentialFade([...this.$refs.list.querySelectorAll('li')])
// using options:
sequentialFade(
// items to fade
[...this.$refs.querySelectorAll('li')],
// object to pass directly to tween options
{
from: { opacity: 0, y: 200 },
to: { opacity: 1, y: 0 }
},
// interval between tween starts, in ms
100
})
}
}
</script>
fh-components also comes with built-in Popmotion transitions. To use, either import the enter
and leave
methods and call in a JS transition. See any entry below for an example.
Mimics jQuery's slideToggle. Example:
<template>
<transition @enter="enter" @leave="leave" :css="false">
<button v-if="shown">Toggle me!</button>
</transition>
</template>
<script>
import { enter, leave } from 'fh-components/transitions/slide-toggle'
export default {
data(){
return {
shown: true
}
},
methods: {
enter,
leave
}
}
</script>
npm test
to run the tests defined in the tests
directory.
We recommend using a single local install to develop for fh-components:
cd ~/your-misc-repo-directory
git clone https://github.com/funkhaus/fh-components
Then, in your Vuehaus instance:
npm link fh-components ~/your-misc-repo-directory/fh-components
This will create a symlink from Vuehaus's npm_modules/fh-components
to the master fh-components install.
Components each get a directory in the /src
folder, with the following naming conventions:
src
+-- new-component
+-- NewComponent.vue
When you're ready to build your component, run npm run build
in fh-components
.
Reusable components are only helpful if they're easy to reuse! Fill in this template to start your documentation, deleting any unnecessary sections.
("SSR Capable" refers to an element being renderable by a server - ie, the element does not rely on document
or window
.)
## `component-name`
`<component-name :example-prop="123"> I'm an example ready to be copied and pasted into a Vue component. </component-name>`
Brief description.
| Standalone | Enhanced by Vuehaus | SSR Capable |
| ---------- | ------------------- | ----------- |
| ✅ | ✅ | ❌ |
**Props**
| Name | Type | Default | Description |
| ---------- | ------ | --------- | --------------- |
| `propName` | `Type` | `default` | About this prop |
**Slots**
| Name | Location |
| ----------- | ---------------------------- |
| `slot-name` | Slot location and extra info |
**Events**
| Name | Signature | Description |
| ----------- | ------------ | ---------------- |
| `eventName` | `(response)` | About this event |
**Classes**
| Name | Conditions | Notes |
| ----------- | --------------------------- | ---------------------------- |
| `className` | When applied, if applicable | Extra notes about this class |
**Notes**
- Miscellaneous notes and information here. Why add this custom component? When can it be used?
It's a good idea to include a test for your new component so that others can add onto it more easily.
Tests each get a .js file in the tests
directory, named the same as the component they test. Here's the template to start writing a new test:
import ComponentName from '../component-name'
import { mount } from '@vue/test-utils'
import tap from 'tap'
// mount component
let wrapper = mount(ComponentName)
tap.test('Component Name', t => {
// Tests using Tap and wrapper here
// ...
t.end()
})
Note the following caveats:
- You'll need to require the component from its built .js file, not its .vue file. (Replacing the
ComponentName
andcomponent-name
in the template should do the trick.) - fh-components uses the node-tap library to run its tests (API).
When you add a new feature to an existing component, be sure to document and test it!
fh-components