Skip to content

feat(select): add custom #tag slot #135

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

Merged
merged 1 commit into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export default defineConfig({
{ text: "Single Select", link: "/demo/single-select" },
{ text: "Multiple Select", link: "/demo/multiple-select" },
{ text: "Custom Option Slot", link: "/demo/custom-option-slot" },
{ text: "Pre-Selected Values (single & multi)", link: "/demo/pre-selected-values" },
{ text: "Custom Tag Slot", link: "/demo/custom-tag-slot" },
{ text: "Pre-Selected Values", link: "/demo/pre-selected-values" },
{ text: "Disabled Options", link: "/demo/disabled-options" },
{ text: "With Menu Header", link: "/demo/with-menu-header" },
{ text: "With Complex Menu Filter", link: "/demo/with-complex-menu-filter.md" },
Expand Down
106 changes: 106 additions & 0 deletions docs/demo/custom-tag-slot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
title: 'Custom Tag Slot'
---

# Custom Tag Slot

The following example demonstrates how to use the `VueSelect` component with a custom slot `#tag` when using the `isMulti` prop.

::: info
Read more about available [slots here](../slots.md) and the `isMulti` prop [here](../props.md#isMulti).
:::

<script setup>
import { ref } from "vue";

import VueSelect from "../../src";

const selected = ref([]);
</script>

<VueSelect
v-model="selected"
:is-multi="true"
:options="[
{ label: 'Alice', value: 'alice', username: '@alice_user' },
{ label: 'John', value: 'john', username: '@john_user' },
{ label: 'Greg', value: 'greg', username: '@greg_user' },
]"
>
<template #tag="{ option, removeOption }">
<div :class="$style['custom-tag']">
{{ option.username }} <button type="button" @click="removeOption">&times;</button>
</div>
</template>
</VueSelect>

<style module>
.custom-tag {
--vs-multi-value-gap: 4px;

display: flex;
align-items: center;
gap: var(--vs-multi-value-gap);
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.1);
padding: var(--vs-multi-value-padding);
margin: var(--vs-multi-value-margin);
color: var(--vs-multi-value-text-color);
line-height: var(--vs-multi-value-line-height);
background: var(--vs-multi-value-bg);
}

.custom-tag button {
font-size: 1.25rem;
background: none;
}
</style>

## Demo source-code

```vue
<script setup>
import { ref } from "vue";
import VueSelect from "vue3-select-component";

const selected = ref([]);
</script>

<VueSelect
v-model="selected"
:is-multi="true"
:options="[
{ label: 'Alice', value: 'alice', username: '@alice_user' },
{ label: 'John', value: 'john', username: '@john_user' },
{ label: 'Greg', value: 'greg', username: '@greg_user' },
]"
>
<template #tag="{ option, removeOption }">
<div class="custom-tag">
{{ option.username }} <button type="button" @click="removeOption">&times;</button>
</div>
</template>
</VueSelect>

<style lang="css" scoped>
.custom-tag {
--vs-multi-value-gap: 4px;

display: flex;
align-items: center;
gap: var(--vs-multi-value-gap);
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.1);
padding: var(--vs-multi-value-padding);
margin: var(--vs-multi-value-margin);
color: var(--vs-multi-value-text-color);
line-height: var(--vs-multi-value-line-height);
background: var(--vs-multi-value-bg);
}

.custom-tag button {
font-size: 1.25rem;
background: none;
}
</style>
```
34 changes: 27 additions & 7 deletions docs/slots.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ If you are not familiar with Vue's slots, you can read more about them [here](ht

**Type**: `slotProps: { option: Option }`

Customize the rendered HTML of an option inside the menu. You can use the slot props to retrieve the current menu option that will be rendered.
Customize the rendered template of an option inside the menu. You can use the slot props to retrieve the current menu option that will be rendered.

```vue
<template>
Expand All @@ -30,7 +30,7 @@ Customize the rendered HTML of an option inside the menu. You can use the slot p

**Type**: `slotProps: { option: Option }`

Customize the rendered HTML if a selected option (inside the select control). You can use the slot props to retrieve the current selected option.
Customize the rendered template if a selected option (inside the select control). You can use the slot props to retrieve the current selected option.

```vue
<template>
Expand All @@ -42,11 +42,31 @@ Customize the rendered HTML if a selected option (inside the select control). Yo
</template>
```

## tag

**Type**: `slotProps: { option: Option, removeOption: () => void }`

When using `isMulti` prop, customize the rendered template of a selected option. You can use the slot props to retrieve the current selected option and a function to remove it.

```vue
<template>
<VueSelect
v-model="option"
:options="options"
:is-multi="true"
>
<template #tag="{ option, removeOption }">
<span>{{ option.label }} <button type="button" @click="removeOption">&times;</button></span>
</template>
</VueSelect>
</template>
```

## menu-header

**Type**: `slotProps: {}`

Customize the rendered HTML for the menu header. This slot is placed **before** the options.
Customize the rendered template for the menu header. This slot is placed **before** the options.

```vue
<template>
Expand All @@ -64,7 +84,7 @@ Customize the rendered HTML for the menu header. This slot is placed **before**

**Type**: `slotProps: {}`

Customize the rendered HTML when there are no options matching the search, inside the menu.
Customize the rendered template when there are no options matching the search, inside the menu.

```vue
<template>
Expand All @@ -80,7 +100,7 @@ Customize the rendered HTML when there are no options matching the search, insid

**Type**: `slotProps: {}`

Customize the rendered HTML for the dropdown icon. Please note that the slot is placed **inside the button**, so you don't have to deal with attaching event-listeners.
Customize the rendered template for the dropdown icon. Please note that the slot is placed **inside the button**, so you don't have to deal with attaching event-listeners.

```vue
<template>
Expand All @@ -96,7 +116,7 @@ Customize the rendered HTML for the dropdown icon. Please note that the slot is

**Type**: `slotProps: {}`

Customize the rendered HTML for the clear icon. Please note that the slot is placed **inside the button**, so you don't have to deal with attaching event-listeners.
Customize the rendered template for the clear icon. Please note that the slot is placed **inside the button**, so you don't have to deal with attaching event-listeners.

```vue
<template>
Expand All @@ -112,7 +132,7 @@ Customize the rendered HTML for the clear icon. Please note that the slot is pla

**Type**: `slotProps: {}`

Customize the rendered HTML when the select component is in a loading state. By default, it displays a `<Spinner />` component.
Customize the rendered template when the select component is in a loading state. By default, it displays a `<Spinner />` component.

```vue
<template>
Expand Down
29 changes: 19 additions & 10 deletions src/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -421,15 +421,24 @@ onBeforeUnmount(() => {
</div>

<template v-if="props.isMulti && selectedOptions.length">
<button
v-for="(option, i) in selectedOptions"
:key="i"
type="button"
class="multi-value"
@click="removeOption(option)"
<template
v-for="selectedOption in selectedOptions"
:key="selectedOption.value"
>
{{ getMultiValueLabel(option) }}<XMarkIcon />
</button>
<slot
name="tag"
:option="selectedOption"
:remove-option="() => removeOption(selectedOption)"
>
<button
type="button"
class="multi-value"
@click="removeOption(selectedOption)"
>
{{ getMultiValueLabel(selectedOption) }}<XMarkIcon />
</button>
</slot>
</template>
</template>

<input
Expand Down Expand Up @@ -716,7 +725,7 @@ onBeforeUnmount(() => {
border: 0;
width: var(--vs-icon-size);
height: var(--vs-icon-size);
fill: var(--vs-icon-color);
color: var(--vs-icon-color);
background: none;
outline: none;
cursor: pointer;
Expand All @@ -730,7 +739,7 @@ onBeforeUnmount(() => {
border: 0;
width: var(--vs-icon-size);
height: var(--vs-icon-size);
fill: var(--vs-icon-color);
color: var(--vs-icon-color);
background: none;
outline: none;
cursor: pointer;
Expand Down