Skip to content

Commit 5c9b9cc

Browse files
authored
feat(select): add classes prop (#244)
Fixes #241
1 parent a2241b6 commit 5c9b9cc

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
@@ -187,13 +187,32 @@ Top and left properties are calculated using a ref on the `.vue-select` with a `
187187

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

190-
## class
190+
## classes
191191

192-
**Type**: `string`
192+
**Type**:
193+
194+
```ts
195+
type SelectClasses = {
196+
container?: string;
197+
control?: string;
198+
valueContainer?: string;
199+
placeholder?: string;
200+
singleValue?: string;
201+
multiValue?: string;
202+
multiValueLabel?: string;
203+
multiValueRemove?: string;
204+
inputContainer?: string;
205+
searchInput?: string;
206+
menuContainer?: string;
207+
menuOption?: string;
208+
noResults?: string;
209+
taggableNoOptions?: string;
210+
};
211+
```
193212

194213
**Default**: `undefined`
195214

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

198217
## uid
199218

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
@@ -27,6 +27,7 @@ const props = withDefaults(
2727
closeOnSelect: true,
2828
teleport: undefined,
2929
inputId: undefined,
30+
classes: undefined,
3031
uid: uniqueId(),
3132
aria: undefined,
3233
disableInvalidVModelWarn: false,
@@ -370,18 +371,18 @@ onBeforeUnmount(() => {
370371
ref="container"
371372
dir="auto"
372373
class="vue-select"
373-
:class="[{ open: menuOpen, typing: menuOpen && search.length > 0, disabled: isDisabled }, props.class]"
374+
:class="[{ open: menuOpen, typing: menuOpen && search.length > 0, disabled: isDisabled }, props.classes?.container]"
374375
:data-state="menuOpen ? 'open' : 'closed'"
375376
>
376377
<div
377378
class="control"
378-
:class="{ focused: menuOpen, disabled: props.isDisabled }"
379+
:class="[{ focused: menuOpen, disabled: props.isDisabled }, props.classes?.control]"
379380
@click="handleControlClick($event)"
380381
>
381382
<div
382383
:id="`vue-select-${uid}-combobox`"
383384
class="value-container"
384-
:class="{ 'multi': isMulti, 'has-value': selectedOptions.length > 0 }"
385+
:class="[{ 'multi': isMulti, 'has-value': selectedOptions.length > 0 }, props.classes?.valueContainer]"
385386
role="combobox"
386387
:aria-expanded="menuOpen"
387388
:aria-describedby="placeholder"
@@ -396,11 +397,13 @@ onBeforeUnmount(() => {
396397
<Placeholder
397398
v-if="!selectedOptions[0] && !search.length"
398399
:text="placeholder"
400+
:class="props.classes?.placeholder"
399401
/>
400402

401403
<div
402404
v-else-if="!props.isMulti && selectedOptions[0]"
403405
class="single-value"
406+
:class="[props.classes?.singleValue]"
404407
@click="openMenu()"
405408
>
406409
<slot name="value" :option="selectedOptions[0]">
@@ -418,20 +421,29 @@ onBeforeUnmount(() => {
418421
:option="selectedOption"
419422
:remove-option="() => removeOption(selectedOption)"
420423
>
421-
<MultiValue :label="getOptionLabel(selectedOption)" @remove="removeOption(selectedOption)" />
424+
<MultiValue
425+
:label="getOptionLabel(selectedOption)"
426+
:classes="{
427+
multiValue: props.classes?.multiValue,
428+
multiValueLabel: props.classes?.multiValueLabel,
429+
multiValueRemove: props.classes?.multiValueRemove,
430+
}"
431+
@remove="removeOption(selectedOption)"
432+
/>
422433
</slot>
423434
</template>
424435

425436
<div
426437
class="input-container"
427-
:class="{ typing: menuOpen && search.length > 0 }"
438+
:class="[{ typing: menuOpen && search.length > 0 }, props.classes?.inputContainer]"
428439
:data-value="search"
429440
>
430441
<input
431442
:id="inputId"
432443
ref="input"
433444
v-model="search"
434445
class="search-input"
446+
:class="props.classes?.searchInput"
435447
autocapitalize="none"
436448
autocomplete="off"
437449
autocorrect="off"
@@ -488,6 +500,7 @@ onBeforeUnmount(() => {
488500
:id="`vue-select-${uid}-listbox`"
489501
ref="menu"
490502
class="menu"
503+
:class="props.classes?.menuContainer"
491504
role="listbox"
492505
:aria-label="aria?.labelledby"
493506
:aria-multiselectable="isMulti"
@@ -508,6 +521,7 @@ onBeforeUnmount(() => {
508521
:is-focused="focusedOption === i"
509522
:is-selected="Array.isArray(selected) ? selected.includes(option.value) : option.value === selected"
510523
:is-disabled="option.disabled || false"
524+
:class="props.classes?.menuOption"
511525
@select="setOption(option)"
512526
>
513527
<slot
@@ -522,7 +536,11 @@ onBeforeUnmount(() => {
522536
</slot>
523537
</MenuOption>
524538

525-
<div v-if="!isTaggable && availableOptions.length === 0" class="no-results">
539+
<div
540+
v-if="!isTaggable && availableOptions.length === 0"
541+
class="no-results"
542+
:class="props.classes?.noResults"
543+
>
526544
<slot name="no-options">
527545
No results found
528546
</slot>
@@ -531,6 +549,7 @@ onBeforeUnmount(() => {
531549
<div
532550
v-if="isTaggable && search"
533551
class="taggable-no-options"
552+
:class="props.classes?.taggableNoOptions"
534553
@click="createOption"
535554
>
536555
<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.
@@ -90,9 +107,9 @@ export type Props<GenericOption, OptionValue> = {
90107
inputId?: string;
91108

92109
/**
93-
* CSS class to append to the select component at the root level.
110+
* CSS classes to apply to the select component.
94111
*/
95-
class?: string;
112+
classes?: SelectClasses;
96113

97114
/**
98115
* Unique identifier to identify the select component, using `id` attribute.

0 commit comments

Comments
 (0)