A collection of useful Vue 2 renderless components.
Check out the demo
yarn add -D @crishellco/vue-spruce
# or
npm i -D @crishellco/vue-spruce
Installs all components globally.
import Vue from 'vue';
import VueSpruce from '@crishellco/vue-spruce';
Vue.use(VueSpruce);
// or with options
Vue.use(VueSpruce, { componentPrefix: 's' });
Installs all components globally.
// nuxt.config.js
{
modules: [['@crishellco/vue-spruce/nuxt', { componentPrefix: 's' }]];
}
Name |
Description |
Default |
componentPrefix |
The prefix used when installing components globally |
spruce |
Alternatively, use only the components you need.
import {
SpruceAtLeast,
SpruceCling,
SpruceEvent,
SpruceFetch,
SpruceFunction,
SprucePaginate,
SpruceSearch,
SpruceSort,
SpruceState,
SpruceToggle,
SpruceWatch,
} from '@crishellco/vue-spruce';
export default {
components: {
SpruceAtLeast,
SpruceCling,
SpruceEvent,
SpruceFetch,
SpruceFunction,
SprucePaginate,
SpruceSearch,
SpruceSort,
SpruceState,
SpruceToggle,
SpruceWatch,
},
};
Ensures a component shows for at least a given amount of time, in milliseconds, before hiding.
<spruce-at-least :ms="5000" :show="shouldShowImage">
<div slot-scope="{ disabled, show }">
<img v-if="show" src="https://placebeard.it/g/200/300" alt="" />
<button :disabled="disabled" @click="shouldShowImage = !shouldShowImage">
{{ disabled ? 'waiting...' : show ? 'hide' : 'show' }}
</button>
</div>
</spruce-at-least>
Name |
Description |
Type |
Required |
Default |
ms |
Minimum amount of time to show, in milliseconds |
Number |
Yes |
|
show |
Weather or not to show the contents (given enough time has passed) |
Boolean |
No |
true |
Name |
Required |
default |
Yes |
Slot |
Name |
Description |
Type |
default |
disabled |
True if waiting to hide content after ms time has passed |
Boolean |
default |
show |
If the contents should be shown |
Boolean |
Clings the clinger
slot's contents to the anchor
slot's contents using popper.js
. Great for things like dropdown menus. See the demo for more context.
<spruce-cling placement="bottom">
<template #anchor>
<button>
i'm a button
</button>
</template>
<template #clinger>
<div>
<div>i'm a clinger!</div>
</div>
</template>
</spruce-cling>
Name |
Required |
anchor |
Yes |
clinger |
Yes |
Slot |
Name |
Description |
Type |
anchor |
update |
Updates the popper instance |
Function |
clinger |
update |
Updates the popper instance |
Function |
Track any window
event occurance inside or outside of SpruceEvent
's default slot.
<spruce-event event="mouseover" @mouseover="someMethod">
<button>Hover over me!</button>
</spruce-event>
Name |
Description |
Type |
Required |
Default |
event |
The event to listen to |
String |
Yes |
|
immediate |
First event immediately (in mounted ) |
Boolean |
No |
false |
outside |
Listen for the even only outside of the default slot elements |
Boolean |
No |
false |
Name |
Description |
Payload |
Same as the event prop |
Fired when the event happens. |
-- |
Make asynchronous API fetch calls.
<spruce-fetch url="https://dog-api.kinduff.com/api/facts" >
<div slot-scope="{ loading, data, error, fetch }">
<loading-indicator v-if="loading" />
<div v-else-if="errors">Errors! {{ error.status }}</div>
<div v-else>Data: {{ data }}</div>
<button @click="fetch">Refetch</button>
<div>
</spruce-fetch>
Name |
Description |
Type |
Required |
Default |
url |
The API url (changing this will refetch and rest all data) |
String |
Yes |
|
immediate |
Weather to immediate make the request on mount |
Boolean |
No |
true |
Name |
Description |
Payload |
success |
Fires when the request is successful |
{data: Object, fetch: Function} |
error |
Fires when the request fails |
{data: Object, fetch: Function} |
Name |
Required |
default |
Yes |
Slot |
Name |
Description |
Type |
default |
calls |
Number of calls made |
Number |
default |
data |
The response on a successful request |
Object |
default |
error |
The response on a failed request |
{ data: Object, status: Number } |
default |
loading |
Whether a request is in progress |
Boolean |
default |
fetch |
Makes another request |
Function |
Create reusable functions on the fly (great for lists!).
<div v-for="num in 10">
<spruce-function :fn="() => alert(num)">
<button slot-scope="{ fn }" @click="fn">Click me!</button>
</spruce-function>
</div>
Name |
Description |
Type |
Required |
Default |
fn |
The function |
Function |
Yes |
|
Name |
Required |
default |
Yes |
Slot |
Name |
Description |
Type |
default |
fn |
The function |
Function |
Paginate an array and navigate through it's chunks.
<spruce-paginate :list="states" :size="15">
<div slot-scope="{ page, next, prev, pageNum, totalPages, isFirst, isLast, rangeStart, rangeEnd }">
<button :disabled="isFirst" @click="prev">
prev
</button>
<div class="px-4 flex flex-col items-center">
<div>Page: {{ pageNum }}/{{ totalPages }}</div>
<div>Showing: {{ rangeStart }} - {{ rangeEnd }} of {{ states.length }}</div>
</div>
<button :disabled="isLast" @click="next">
next
</button>
</div>
</spruce-paginate>
Name |
Description |
Type |
Required |
Default |
size |
Page size |
Number |
Yes |
|
list |
The items to paginate |
Array |
Yes |
|
Name |
Required |
default |
Yes |
Slot |
Name |
Description |
Type |
default |
first |
Go to first page |
Function |
default |
go |
Go to specific page |
Function |
default |
isFirst |
If currently on first page |
Boolean |
default |
isLast |
If currently on last page |
Boolean |
default |
last |
Go to last page |
Function |
default |
next |
Go to next page |
Function |
default |
page |
The current page |
any |
default |
pages |
The chunked pages |
Array |
default |
links |
A calculated array of specific page numbers that can be used for links [1, 2, 3, '...', 40] |
Array |
default |
pageNum |
The current page number |
Number |
default |
prev |
Go to previous page |
Function |
default |
rangeEnd |
The end of the current page |
Number |
default |
rangeStart |
The start of the current page |
Number |
default |
reset |
Reset the state of pagination |
Function |
default |
totalPages |
Total number of pages |
Number |
Search an array of strings or objects by keys using fuse.js.
<spruce-search :list="states" :term="term" :keys="['name', 'email']">
<div slot-scope="{ results }">
<div v-for="(item, index) in results" :key="index">
{{ item }}
</div>
</div>
</spruce-search>
Name |
Description |
Type |
Required |
Default |
keys |
The keys to search in |
Array |
No |
If list is Array<Object> then all of the first object's keys. Otherwise [] . |
list |
The list to search |
Array |
Yes |
|
term |
The terms to search for |
String |
No |
'' |
threshold |
Fuse.js match threshold |
Float |
No |
0.6 |
Name |
Required |
default |
Yes |
Slot |
Name |
Description |
Type |
default |
results |
The searched list |
Array |
Sort an array of strings or objects in either direction by specific keys.
Note: string sorting is case insensitive.
<spruce-sort :list="people" :by="by" :direction="direction">
<div slot-scope="{ results }">
<div v-for="(item, index) in results" :key="index">
{{ item }}
</div>
</div>
</spruce-sort>
Name |
Description |
Type |
Required |
Default |
list |
The list to search |
Array |
Yes |
|
direction |
The direction to sort in, 'asc' or 'desc' |
String |
No |
'asc' |
by |
The object property to sort by |
String |
No |
'' |
Name |
Description |
Payload |
change |
Fired when results change (the list is sorted). |
results |
Name |
Required |
default |
Yes |
Slot |
Name |
Description |
Type |
default |
results |
The searched list |
Array<String, Object> |
Create and manage localized state.
<spruce-state :value="{ count: 0 }">
<div slot-scope="{ count, set }">
<button @click="set({count: count + 1})">
Increment ({{ count }})
</button>
</div>
</spruce-state>
Name |
Description |
Type |
Required |
Default |
value |
The state object |
Object |
Yes |
|
Name |
Description |
Payload |
input |
Fired when state updates |
state |
Name |
Required |
default |
Yes |
Slot |
Name |
Description |
Type |
default |
[key] |
Each key in the state prop |
Any |
default |
set(newValue) |
Merges newValue with the current state |
Function |
Renderless tag input.
<template>
<spruce-tag-input v-model="colors" :validator="validator">
<div
slot-scope="{ events, focusedTagIndex, invalid, remove, state, tags }"
:class="{ 'border-red-500': invalid }"
>
<button
v-for="(tag, index) in tags"
:key="index"
type="button"
:class="{ 'bg-red-500': focusedTagIndex === index }"
title="Remove tag"
@click="remove(tag)"
>
<span>{{ tag }}</span>
<span>×</span>
</button>
<input v-bind="state" placeholder="Add tag with letters only..." v-on="events" />
</div>
</spruce-tag-input>
</template>
<script>
export default {
data() {
return { colors: ['red', 'blue'] };
},
methods: {
validator(tag) {
return /^[a-zA-Z]+$/.test(tag);
},
},
};
</script>
Name |
Description |
Type |
Required |
Default |
allowDuplicates |
Allows duplicate tags |
Boolean |
No |
False |
allowPaste |
Allows pasting to automatically create tags |
Boolean |
No |
False |
disabled |
Disables all interactions |
Boolean |
No |
False |
keepOnBackspace |
Disables deleting last tab on keyup.backspace in the input |
Boolean |
No |
False |
maxTags |
Number of allowed tags |
Number |
No |
Null |
separator |
Separator used to process pasted tags |
String |
No |
\t |
v-model |
The tags |
Array |
Yes |
|
validator |
Function that receives the String argument tag and returns true or false to determine the validity of the input's value |
Function |
No |
() => true |
Slot |
Name |
Description |
Type |
default |
events |
Events to listen for on the input. input for binding value, keydown.backspace for delete last tag, keydown.enter for adding new unique tag, and keydown.escape for clearing input. |
Object |
default |
focusedTagIndex |
Currently focused tag index (to be removed on next keydown.backspace ). Used for styling. |
String |
default |
remove() |
Removes a tag |
Function |
default |
state |
State to bind to the input. value of the input. |
Object |
default |
tags |
Array of tags |
Array |
Toggle between on (true
) and off (false
).
<spruce-toggle :value="true">
<div slot-scope="{ isOn, on, off, toggle }">
<div>
<span>Accordion header</span>
<span @click="toggle">{{ isOn ? '▲' : '▼' }}</span>
</div>
<div v-if="isOn">
Accordion content
</div>
<div>
<button @click="on">Open</button>
<button @click="off">Close</button>
</div>
</div>
</spruce-toggle>
Name |
Description |
Type |
Required |
Default |
value |
The state of the toggle |
Boolean |
No |
False |
Name |
Description |
Payload |
input |
Fired when isOn updates |
isOn |
Name |
Required |
default |
Yes |
Slot |
Name |
Description |
Type |
default |
isOn |
The state of the toggle |
Boolean |
default |
on() |
Sets isOn to true |
Function |
default |
off() |
Sets isOn to false |
Function |
default |
toggle() |
Toggles isOn |
Function |
Watches variables for changes and emits events when changes occur.
<spruce-watch :watch="{count}" @changed="handleAnyChange" @changed:count="handleCountChange">
<button @click="count++">
count++ ({{ count }})
</button>
</spruce-watch>
Name |
Description |
Type |
Required |
Default |
watch |
Values you wish to watch |
Object |
Yes |
|
Name |
Description |
Payload |
changed |
Fired when any value in watch changes |
{key: count, oldValue: 0, newValue: 1} |
changed:[key] |
Fired when a specific value in watch changes |
{oldValue: 0, newValue: 1} |
See the demo source code for real-world examples. Check out the demo to see the components in action with code examples.
- Fork the repository
- Create a new branch for each feature or improvement
- Send a pull request from each feature branch to the develop branch
MIT