Skip to content

Commit 846840d

Browse files
committed
feat(select): add classes prop
Fixes #241
1 parent ff1dc37 commit 846840d

File tree

5 files changed

+112
-15
lines changed

5 files changed

+112
-15
lines changed

docs/props.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,32 @@ Top and left properties are calculated using a ref on the `.vue-select` with a `
179179

180180
The `id` attribute to be passed to the `<input />` element. This is useful for accessibility or forms.
181181

182-
## class
182+
## classes
183183

184-
**Type**: `string`
184+
**Type**:
185+
186+
```ts
187+
type SelectClasses = {
188+
container?: string;
189+
control?: string;
190+
valueContainer?: string;
191+
placeholder?: string;
192+
singleValue?: string;
193+
multiValue?: string;
194+
multiValueLabel?: string;
195+
multiValueRemove?: string;
196+
inputContainer?: string;
197+
searchInput?: string;
198+
menuContainer?: string;
199+
menuOption?: string;
200+
noResults?: string;
201+
taggableNoOptions?: string;
202+
};
203+
```
185204

186205
**Default**: `undefined`
187206

188-
A custom class to be passed to the select control.
207+
CSS classes to be applied at multiple places in the select component. Useful when using TailwindCSS to customize the component.
189208

190209
## uid
191210

docs/styling.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,41 @@ You can also use the `:deep` selector to apply the CSS variables to the componen
123123
</style>
124124
```
125125

126+
## Custom classes with TailwindCSS
127+
128+
The component provides a `classes` prop that allows you to apply custom TailwindCSS classes to different parts of the select component. This is particularly useful when you want to customize the appearance without overriding the default CSS variables.
129+
130+
Here's an example of how to use TailwindCSS classes with the component:
131+
132+
```vue
133+
<template>
134+
<VueSelect
135+
v-model="selected"
136+
:options="options"
137+
:classes="{
138+
container: 'w-full max-w-md',
139+
control: 'border-2 border-gray-200 hover:border-gray-300',
140+
valueContainer: 'p-2',
141+
placeholder: 'text-gray-400',
142+
singleValue: 'text-gray-800 font-medium',
143+
multiValue: 'bg-blue-100 rounded-md',
144+
multiValueLabel: 'text-blue-800 px-2 py-1',
145+
multiValueRemove: 'hover:bg-blue-200 px-2',
146+
inputContainer: 'p-1',
147+
searchInput: 'text-gray-700',
148+
menuContainer: 'mt-1 border border-gray-200 rounded-md shadow-lg',
149+
menuOption: 'px-3 py-2 hover:bg-gray-100',
150+
noResults: 'text-gray-500 p-3',
151+
taggableNoOptions: 'text-blue-600 p-3 hover:bg-blue-50',
152+
}"
153+
/>
154+
</template>
155+
```
156+
157+
::: warning
158+
When using TailwindCSS classes, be careful not to break the component's functionality by overriding essential styles like `display`, `position`, or `z-index` properties that are crucial for the component's layout and behavior.
159+
:::
160+
126161
## Scoped styling inside SFC
127162

128163
You can apply any custom styling using [the `:deep` selector](https://vuejs.org/api/sfc-css-features.html#deep-selectors) inside a `<style scoped>`.

src/MultiValue.vue

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
<script setup lang="ts">
22
import XMarkIcon from "./icons/XMarkIcon.vue";
33
4-
defineProps<{
4+
const props = defineProps<{
55
label: string;
6+
classes?: {
7+
multiValue?: string;
8+
multiValueLabel?: string;
9+
multiValueRemove?: string;
10+
};
611
}>();
712
813
const emit = defineEmits<{
@@ -13,15 +18,17 @@ const emit = defineEmits<{
1318
<template>
1419
<div
1520
class="multi-value"
21+
:class="props.classes?.multiValue"
1622
>
17-
<div class="multi-value-label">
18-
{{ label }}
23+
<div class="multi-value-label" :class="props.classes?.multiValueLabel">
24+
{{ props.label }}
1925
</div>
2026

2127
<button
2228
type="button"
2329
class="multi-value-remove"
24-
:aria-label="`Remove ${label}`"
30+
:class="props.classes?.multiValueRemove"
31+
:aria-label="`Remove ${props.label}`"
2532
@click.stop="emit('remove')"
2633
>
2734
<XMarkIcon />

src/Select.vue

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const props = withDefaults(
2626
closeOnSelect: true,
2727
teleport: undefined,
2828
inputId: undefined,
29+
classes: undefined,
2930
uid: uniqueId(),
3031
aria: undefined,
3132
disableInvalidVModelWarn: false,
@@ -362,18 +363,18 @@ onBeforeUnmount(() => {
362363
ref="container"
363364
dir="auto"
364365
class="vue-select"
365-
:class="[{ open: menuOpen, typing: menuOpen && search.length > 0, disabled: isDisabled }, props.class]"
366+
:class="[{ open: menuOpen, typing: menuOpen && search.length > 0, disabled: isDisabled }, props.classes?.container]"
366367
:data-state="menuOpen ? 'open' : 'closed'"
367368
>
368369
<div
369370
class="control"
370-
:class="{ focused: menuOpen, disabled: props.isDisabled }"
371+
:class="[{ focused: menuOpen, disabled: props.isDisabled }, props.classes?.control]"
371372
@click="handleControlClick($event)"
372373
>
373374
<div
374375
:id="`vue-select-${uid}-combobox`"
375376
class="value-container"
376-
:class="{ 'multi': isMulti, 'has-value': selectedOptions.length > 0 }"
377+
:class="[{ 'multi': isMulti, 'has-value': selectedOptions.length > 0 }, props.classes?.valueContainer]"
377378
role="combobox"
378379
:aria-expanded="menuOpen"
379380
:aria-describedby="placeholder"
@@ -388,11 +389,13 @@ onBeforeUnmount(() => {
388389
<Placeholder
389390
v-if="!selectedOptions[0] && !search.length"
390391
:text="placeholder"
392+
:class="props.classes?.placeholder"
391393
/>
392394

393395
<div
394396
v-else-if="!props.isMulti && selectedOptions[0]"
395397
class="single-value"
398+
:class="[props.classes?.singleValue]"
396399
@click="openMenu()"
397400
>
398401
<slot name="value" :option="selectedOptions[0]">
@@ -410,20 +413,29 @@ onBeforeUnmount(() => {
410413
:option="selectedOption"
411414
:remove-option="() => removeOption(selectedOption)"
412415
>
413-
<MultiValue :label="getOptionLabel(selectedOption)" @remove="removeOption(selectedOption)" />
416+
<MultiValue
417+
:label="getOptionLabel(selectedOption)"
418+
:classes="{
419+
multiValue: props.classes?.multiValue,
420+
multiValueLabel: props.classes?.multiValueLabel,
421+
multiValueRemove: props.classes?.multiValueRemove,
422+
}"
423+
@remove="removeOption(selectedOption)"
424+
/>
414425
</slot>
415426
</template>
416427

417428
<div
418429
class="input-container"
419-
:class="{ typing: menuOpen && search.length > 0 }"
430+
:class="[{ typing: menuOpen && search.length > 0 }, props.classes?.inputContainer]"
420431
:data-value="search"
421432
>
422433
<input
423434
:id="inputId"
424435
ref="input"
425436
v-model="search"
426437
class="search-input"
438+
:class="props.classes?.searchInput"
427439
autocapitalize="none"
428440
autocomplete="off"
429441
autocorrect="off"
@@ -480,6 +492,7 @@ onBeforeUnmount(() => {
480492
:id="`vue-select-${uid}-listbox`"
481493
ref="menu"
482494
class="menu"
495+
:class="props.classes?.menuContainer"
483496
role="listbox"
484497
:aria-label="aria?.labelledby"
485498
:aria-multiselectable="isMulti"
@@ -500,14 +513,19 @@ onBeforeUnmount(() => {
500513
:is-focused="focusedOption === i"
501514
:is-selected="option.value === selected"
502515
:is-disabled="option.disabled || false"
516+
:class="props.classes?.menuOption"
503517
@select="setOption(option)"
504518
>
505519
<slot name="option" :option="option">
506520
{{ getOptionLabel(option) }}
507521
</slot>
508522
</MenuOption>
509523

510-
<div v-if="!isTaggable && availableOptions.length === 0" class="no-results">
524+
<div
525+
v-if="!isTaggable && availableOptions.length === 0"
526+
class="no-results"
527+
:class="props.classes?.noResults"
528+
>
511529
<slot name="no-options">
512530
No results found
513531
</slot>
@@ -516,6 +534,7 @@ onBeforeUnmount(() => {
516534
<div
517535
v-if="isTaggable && search"
518536
class="taggable-no-options"
537+
:class="props.classes?.taggableNoOptions"
519538
@click="createOption"
520539
>
521540
<slot

src/types.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ export type Option<T> = {
55
[key: string]: unknown;
66
};
77

8+
export type SelectClasses = {
9+
container?: string;
10+
control?: string;
11+
valueContainer?: string;
12+
placeholder?: string;
13+
singleValue?: string;
14+
multiValue?: string;
15+
multiValueLabel?: string;
16+
multiValueRemove?: string;
17+
inputContainer?: string;
18+
searchInput?: string;
19+
menuContainer?: string;
20+
menuOption?: string;
21+
noResults?: string;
22+
taggableNoOptions?: string;
23+
};
24+
825
export type Props<GenericOption, OptionValue> = {
926
/**
1027
* A list of options to render on the select component.
@@ -84,9 +101,9 @@ export type Props<GenericOption, OptionValue> = {
84101
inputId?: string;
85102

86103
/**
87-
* CSS class to append to the select component at the root level.
104+
* CSS classes to apply to the select component.
88105
*/
89-
class?: string;
106+
classes?: SelectClasses;
90107

91108
/**
92109
* Unique identifier to identify the select component, using `id` attribute.

0 commit comments

Comments
 (0)