Skip to content

Commit

Permalink
Merge pull request #13 from mayank1513/update-docs
Browse files Browse the repository at this point in the history
Update docs + update styles, support passing custom input params
  • Loading branch information
mayank1513 authored Sep 12, 2023
2 parents 603b97b + 595428f commit 7e45880
Show file tree
Hide file tree
Showing 12 changed files with 455 additions and 69 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
name: test

on: [push, pull_request]

on:
push:
pull_request:
schedule:
- cron: '0 8 * * *' # every 30 minutes
jobs:
test:
runs-on: ubuntu-latest
Expand Down
59 changes: 46 additions & 13 deletions lib/TagInput.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch, nextTick, onMounted, computed } from "vue";
import { ref, watch, nextTick, onMounted, computed, InputHTMLAttributes } from "vue";
defineOptions({ name: "TagInput" });
export interface TagInputProps {
modelValue: string[];
Expand All @@ -10,7 +10,8 @@ export interface TagInputProps {
tagBgColor?: string;
tagClass?: string;
customDelimiter?: string[] | string;
singleLine: boolean
singleLine?: boolean
inputProps?: InputHTMLAttributes
}
const props = withDefaults(defineProps<TagInputProps>(), {
Expand All @@ -30,7 +31,7 @@ const tags = ref<string[]>(props.modelValue);
const tagsClass = ref(props.tagClass);
const newTag = ref("");
const focused = ref(false);
const id = Math.random().toString(36).substring(7);
const activeOptionInd = ref(-1);
const customDelimiter = computed<string[] | string>(() => [
...new Set(
(typeof props.customDelimiter == "string"
Expand All @@ -56,6 +57,7 @@ function handleNoMatchingTag() {
newTag.value = v.slice(0, v.length - 1);
}
const addTag = (tag: string) => {
console.log({ tag })
tag = tag.trim();
if (!tag) return; // prevent empty tag
// only allow predefined tags when allowCustom is false
Expand All @@ -71,6 +73,7 @@ const addTag = (tag: string) => {
}
tags.value.push(tag);
newTag.value = ""; // reset newTag
activeOptionInd.value = -1;
};
const addTagIfDelem = (tag: string) => {
if (!customDelimiter.value || customDelimiter.value.length == 0) return;
Expand All @@ -96,8 +99,8 @@ onMounted(onTagsChange);
// options
const availableOptions = computed(() => {
if (!props.options) return false;
return props.options.filter((option) => !tags.value.includes(option));
if (!props.options) return [];
return props.options.filter((option) => newTag.value && !tags.value.includes(option) && option.match(new RegExp(newTag.value, 'i')));
});
const shouldDelete = ref<boolean>(false);
Expand All @@ -116,7 +119,8 @@ const deleteLastTag = () => {
}
}
};
const inputElId = `tag-input${Math.random()}`
const id = Math.random().toString(36).substring(7);
const inputElId = `tag-input${id}`
</script>

<template>
Expand All @@ -132,15 +136,19 @@ const inputElId = `tag-input${Math.random()}`
<button class="delete" @click="removeTag(index)">x</button>
</li>
<div class="tag-input">
<input v-model="newTag" :id="inputElId" type="text" :list="id" autocomplete="off" @keydown.enter="addTag(newTag)"
<input v-model="newTag" :id="inputElId" type="text" autocomplete="off"
@keydown.enter="addTag(activeOptionInd > -1 ? availableOptions[activeOptionInd] : newTag)"
@keydown.prevent.tab="addTag(newTag)" @keydown.delete="deleteLastTag()" @input="addTagIfDelem(newTag)"
placeholder="Enter tag" @focus="focused = true" @blur="focused = false" />
@keydown.down="activeOptionInd = (activeOptionInd + 1) % availableOptions.length"
@keydown.up="activeOptionInd = (availableOptions.length + activeOptionInd - 1) % availableOptions.length"
placeholder="Enter tag" @focus="focused = true" @blur="focused = false" v-bind="inputProps" />

<datalist v-if="options" :id="id">
<option v-for="option in availableOptions" :key="option" :value="option">
<ul class="options">
<li v-for="(option, i) in availableOptions" :key="option" @click="addTag(option)"
:class="{ active: i === activeOptionInd }">
{{ option }}
</option>
</datalist>
</li>
</ul>
</div>
<div v-if="showCount" class="count">
<span>{{ tags.length }}</span> tags
Expand All @@ -155,6 +163,32 @@ const inputElId = `tag-input${Math.random()}`
box-sizing: border-box;
}
.options {
position: absolute;
top: 35px;
list-style-type: none;
padding: 0;
visibility: hidden;
transition: visibility 1s;
overflow: auto;
}
input:focus~.options {
visibility: visible;
}
.options li {
padding: 10px;
background: #333;
color: #eee;
cursor: pointer;
}
.options li:hover,
.options li.active {
background: #555;
}
.tag-input {
position: relative;
width: 250px;
Expand All @@ -171,7 +205,6 @@ const inputElId = `tag-input${Math.random()}`
margin: 0;
padding: 10px;
left: 10px;
max-width: 75%;
border-bottom: 1px solid #5558;
cursor: text;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"author": "Mayank Kumar Chaudhari <https://mayank-chaudhari.vercel.app>",
"private": false,
"description": "A versatile tag input component built with Vue 3 Composition API",
"version": "1.0.5",
"version": "1.1.0",
"type": "module",
"repository": {
"type": "git",
Expand Down
99 changes: 87 additions & 12 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
<script setup lang="ts">
import { RouterView, RouterLink } from "vue-router";
import { computed } from "vue";
import { RouterView, RouterLink, useRoute } from "vue-router";
const examples = [
["Autocomplete", "autocomplete"],
["Custom Delimeter", "custom-delimeter"],
["Input Params", "custom-placeholder"]
];
const path = computed(() => useRoute().path);
</script>

<template>
Expand All @@ -9,15 +17,30 @@ import { RouterView, RouterLink } from "vue-router";
A versatile tag input component built with Vue 3 Composition API.
<hr />
<p>
<router-link to="/">Home</router-link>
<router-link to="/getting-started">Getting Started</router-link>
<details>
<summary>Examples</summary>
</details>
<router-link to="/" :class="{ active: path === '/' }">Features</router-link>
<router-link to="/getting-started" :class="{ active: path === '/getting-started' }">Getting Started</router-link>
<details open>
<summary>Examples</summary>
<router-link v-for="example in examples" :to="`/examples/${example[1]}`"
:class="{ active: path === `/examples/${example[1]}` }">{{ example[0] }}</router-link>
</details>
</p>
</aside>
<main>
<router-view />

<div class="grow"></div>
<footer>
<p>
License:
<a href="https://github.com/mayank1513/tag-input/blob/master/LICENSE" target="_blank"
rel="noopener noreferrer">MIT</a>
<br />
<br />
Copyright © 2023
<a href="https://mayank-chaudhari.vercel.app" target="_blank">Mayank Kumar Chaudhari</a>
</p>
</footer>
</main>
</div>
</template>
Expand All @@ -28,43 +51,95 @@ body {
padding: 0;
margin: 0;
}
a {
color: inherit;
display: block;
padding: 10px;
details {
padding: 15px;
summary {
margin-left: -5px;
padding-bottom: 5px;
cursor: pointer;
}
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
.container {
display: flex;
height: 100vh;
width: 100vw;
overflow: hidden;
}
.container aside {
width: 400px;
$w: 300px;
width: $w;
min-width: $w;
max-width: $w;
background: #1e2a31;
overflow: auto;
padding: 10px;
color: #b0c4cf;
box-shadow: 0 0 5px #1e2a31;
a {
color: inherit;
display: block;
padding: 10px 60px;
text-decoration: none;
margin: 0 -50px;
&.active,
&:hover {
font-weight: bold;
background: #ff52;
}
&.active {
background: #ff55;
}
}
p {
text-align: start;
}
}
pre {
padding: 10px 15px;
width: 900px;
border-radius: 5px;
background: #5552;
border: 1px solid #0005;
font-size: 16px;
font-weight: 500;
overflow: auto;
}
.container main {
display: flex;
flex-direction: column;
text-align: start;
overflow: auto;
flex-grow: 1;
padding: 10px;
padding: 20px 30px;
padding-bottom: 10px;
}
.grow {
flex-grow: 1;
}
@media (max-width: 600px) {
.container {
flex-direction: column;
aside {
height: 320px;
overflow: hidden;
Expand Down
92 changes: 92 additions & 0 deletions src/pages/Features.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<script setup lang="ts">
import { ref } from "vue";
import TagInput from "../../lib/TagInput.vue";
import "@mayank1513/vue-tag-input/style.css";
const options = [
"No dependencies",
"Autocompletion",
"Keep Focused",
"Fast Settup",
"Mini Sized",
"Customizable",
"Backspace/Delete to remove tag",
"Turns red when backspace/delete is pressed",
"Examples",
"Docs",
"Copy/Paste",
]
const tags = ref<string[]>([...options]);
</script>

<template>
<div class="main">
<h1>Vue Tag Input</h1>
A versetile tag input component built with Vue 3 Composition API.
<p dir="auto" class="git-tags">
<a href="https://www.npmjs.com/package/@mayank1513/vue-tag-input" rel="nofollow"><img
src="https://camo.githubusercontent.com/b3cefbae109b4ce0f8ae576eeec878b6e4761780cdb5c1f6508e8f6133d7ef57/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f406d6179616e6b313531332f7675652d7461672d696e7075742e7376673f636f6c6f72423d677265656e"
alt="Version" data-canonical-src="https://img.shields.io/npm/v/@mayank1513/vue-tag-input.svg?colorB=green"
style="max-width: 100%" /></a>
<a href="https://codecov.io/gh/mayank1513/tag-input" rel="nofollow"><img
src="https://camo.githubusercontent.com/43b7960de497ebbf7b33571e3b30fb1bfc9827125d0d7d8aebdda3f2bbc8ae35/68747470733a2f2f636f6465636f762e696f2f67682f6d6179616e6b313531332f7461672d696e7075742f67726170682f62616467652e737667"
alt="codecov" data-canonical-src="https://codecov.io/gh/mayank1513/tag-input/graph/badge.svg"
style="max-width: 100%" /></a>
<a href="https://www.npmjs.com/package/@mayank1513/vue-tag-input" rel="nofollow"><img
src="https://camo.githubusercontent.com/ac8040288c4017663796556c36c2ba7445de7a2b216e0ff837b315bb2df91169/68747470733a2f2f696d672e6a7364656c6976722e636f6d2f696d672e736869656c64732e696f2f6e706d2f64742f406d6179616e6b313531332f7675652d7461672d696e7075742e737667"
alt="Downloads"
data-canonical-src="https://img.jsdelivr.com/img.shields.io/npm/dt/@mayank1513/vue-tag-input.svg"
style="max-width: 100%" /></a>
<a href="https://www.npmjs.com/package/@mayank1513/vue-tag-input" rel="nofollow"><img
src="https://camo.githubusercontent.com/edf5ba88cf7dc251c58b4311f905f16345a57570992d1cccd4fe14a024fb9ffa/68747470733a2f2f696d672e736869656c64732e696f2f62756e646c6570686f6269612f6d696e7a69702f406d6179616e6b313531332f7675652d7461672d696e707574"
alt="npm bundle size" data-canonical-src="https://img.shields.io/bundlephobia/minzip/@mayank1513/vue-tag-input"
style="max-width: 100%" /></a>
<a href="https://github.com/mayank1513/tag-input/actions/workflows/publish-to-npm-on-new-release.yml"><img
src="https://github.com/mayank1513/tag-input/actions/workflows/test.yml/badge.svg"
alt="Publish to npm and GitHub" style="max-width: 100%" /></a>
<a href="https://www.codementor.io/@mayank1513?refer=badge" rel="nofollow"><img
src="https://github.com/mayank1513/tag-input/raw/master/codementor.svg" alt="Get help"
style="max-width: 100%" /></a>
</p>

<br />
<tag-input v-model="tags" :options="options" />
<ul dir="auto">
<li>✅ No dependencies</li>
<li>
✅ Input box stays focused - no need to re-focus the input =&gt; better
UX
</li>
<li>✅ Autocompletion</li>
<li>✅ Fast setup</li>
<li>✅ Works with Vuex</li>
<li>✅ Small size: 1.6 kB gzipped</li>
<li>✅ Many customization options</li>
<li>✅ Delete tags on backspace / delete key</li>
<li>
✅ Confirm before delete - tags turns red when backspace is pressed,
gets deleted when backspace is pressed again
</li>
<li>✅ Works well with copy &amp; paste</li>
<li>✅ Examples &amp; Docs</li>
</ul>
</div>
</template>

<style scoped>
.main {
display: flex;
flex-direction: column;
text-align: start;
max-width: 1100px;
}
.grow {
flex-grow: 1;
}
.git-tags {
display: flex;
gap: 15px;
}
</style>
Loading

0 comments on commit 7e45880

Please sign in to comment.