Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vuetify v-select component width changing

My v-select components should have a fixed width (60px), they fit in a table cell, and I want to prevent them from changing the width after value selected.

They change the width and drop-down arrow moves to the right after selection, so if there a way to decrease the size of an icon or its padding/margin it might be helpful.

Don't really know how to get props of this arrow and how this calls.

Here is the reproducible

https://codesandbox.io/s/competent-dew-eixq2?file=/src/components/Playground.vue

EDIT: Add snippet

<!DOCTYPE html>
<html>

<head>
  <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
  <style>
    .select {
      max-width: 60px;
      max-height: 60px;
      font-size: 11px;
    }
    
    .col {
      max-width: 60px;
      max-height: 60px;
    }
  </style>
</head>

<body>
  <div id="app">
    <v-app>
      <v-row>
        <div class="col" v-for="col in cols" :key="col">
          <v-select class="select" :items="variants" item-value="name" item-text="name" label="" dense outlined hide-details single-line v-model="selected">
          </v-select>
        </div>
      </v-row>
    </v-app>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.js"></script>
  <script>
    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      data: {
        selected: "",
        cols: [1, 2, 3, 4, 5],
        variants: [{
            id: 0,
            name: ""
          },
          {
            id: 1,
            name: "1:0"
          },
          {
            id: 2,
            name: "0:1"
          },
          {
            id: 3,
            name: "1:0 B"
          },
          {
            id: 4,
            name: "0:1 B"
          },
          {
            id: 6,
            name: "1:0 R"
          },
          {
            id: 7,
            name: "0:1 R"
          },
          {
            id: 8,
            name: "1:0 F"
          },
          {
            id: 9,
            name: "0:1 F"
          },
        ],
      },
    })
  </script>
</body>

</html>
like image 574
karambaq Avatar asked Jun 21 '21 20:06

karambaq


2 Answers

The basic problem is that v-select has some styling (specifically padding and margin) that does not work very well at small widths.

This is the innerHTML of the rendered v-select, with the styles that need reducing

<div class="select...">                                      
  <div class="v-input__control">
    <div role="button" class="v-input__slot">                <!-- padding-right: 12px; -->
      <div class="v-select__slot">
        <div class="v-select__selections">                               
          <div class="v-select__selection--comma">           <!-- margin-right: 4px; -->
            1:0 B
          </div>
        </div>
        <div class="v-input__append-inner">                  <!-- padding-left: 4px; -->
          <div class="v-input__icon v-input__icon--append">  <!-- width: 24px; min-width: 24px;-->
            <i aria-hidden="true" class="v-icon..."></i>                 
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Aside from those innerHTML changes, you want a fixed width of 60px per column so change

<style>
  .select {
    max-width: 60px;
    ...
  }

to

<style>
  .select {
    width: 60px;
    ...
  }

Adjusting Vuetify inner styles

Looking at the Vuetify issues around styling, there's suggestions of using un-scoped style blocks, or deep-scoped style blocks, but neither worked for me. Vuetify have said they are working on a revamp of the way styles are applied to overcome the issues.

Fortunately Vue itself has tools to do it in javascript.

These are the key steps

  • add a reference to the v-select so that it's accessible in javascript
  • add a change handler to the v-select so that elements can be re-styled when the user selects something
  • define the style adjustments in an object so you can easily tweak them
  • add a method to apply the styles
  • call the method in mounted() and the v-select @change handler
  • use Vue.nextTick() to allow Vuetify to style first, then our custom styles are applied

Here's the adjusted code snippet. I put in some severely minimal padding and margins, and maximized the space for the selected value (to avoid text wrapping). You may want to play with the styles as I'm not sure I understood all of the requirements.

<!DOCTYPE html>
<html>

<head>
  <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
  <style>
    .select {
      width: 60px;
      max-height: 60px;
      font-size: 11px;
    }
    
    .col {
      max-width: 60px;
      max-height: 60px;
    }
  </style>
</head>

<body>
  <div id="app">
    <v-app>
      <v-row>
        <div class="col" v-for="col in cols" :key="col">
          <v-select ref="select" @change="applyCustomStyles"
 class="select" :items="variants" item-value="name" item-text="name" label="" dense outlined hide-details single-line v-model="selected">
          </v-select>
        </div>
      </v-row>
    </v-app>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.js"></script>
  <script>
    const customStyles = {
      ".v-input__slot": {
        padding: "0 0 0 4px",
      },
      ".v-select__selections": {
        width: "27px",
      },
      ".v-select__selection--comma": {
        margin: "7px 0 7px 0",
      },
      ".v-input__append-inner": {
        "padding-left": "0",
      },
      ".v-input__icon": {
        width: "14px",
        "min-width": "14px",
      },
    };

    new Vue({
      el: '#app',
      vuetify: new Vuetify(),
      mounted() {
        this.applyCustomStyles();
      },
      methods: {
        applyCustomStyles() {
          Vue.nextTick(() => {
            this.$refs.select.forEach((vSelect) => {
              Object.entries(customStyles).forEach(([selector, styles]) => {
                Object.entries(styles).forEach(([style, value]) => {
                  vSelect.$el.querySelector(selector).style[style] = value;
                });
              });
            });
          });
        },
      },
      data: {
        selected: "",
        cols: [1, 2, 3, 4, 5],
        variants: [{
            id: 0,
            name: ""
          },
          {
            id: 1,
            name: "1:0"
          },
          {
            id: 2,
            name: "0:1"
          },
          {
            id: 3,
            name: "1:0 B"
          },
          {
            id: 4,
            name: "0:1 B"
          },
          {
            id: 6,
            name: "1:0 R"
          },
          {
            id: 7,
            name: "0:1 R"
          },
          {
            id: 8,
            name: "1:0 F"
          },
          {
            id: 9,
            name: "0:1 F"
          },
        ],
      },
    })
  </script>
</body>

</html>

Why not a <style> block?

There are suggestions of using an un-scoped style block to override the Vuetify styles.

For example,

<style>
  .select .v-input__slot {
   padding-right: 4px
  }
  ...
</style>

The problem is the Vuetify styles are getting applied after those declared on the component.

enter image description here

You can do it by applying greater specificity than Vuetify uses, e.g.

<style>
  .select.v-text-field.v-text-field--enclosed:not(.v-text-field--rounded)>.v-input__control>.v-input__slot {
  padding-right: 4px
}
</style>

gives you this

enter image description here

so you can hunt out all the existing places where a change is necessary and copy the Vuetify selector.

The problems might occur when a new version of Vuetify is used, or the shape of the component is changed. To me, the javascript solution looks more manageable.

like image 80
Richard Matsen Avatar answered Sep 22 '22 20:09

Richard Matsen


1. If you don't care about the appended icon, you can remove it with the use of append-icon prop (pass empty value):

<v-select
    class="select"
    ...
    append-icon=""
></v-select>

2. You can override the slot for the icon with your own content:

<v-select
    class="select"
    ...
>
   <template #append>
      <div class="my-custom-icon">...</div>
   </template>
</v-select>

Then add style to your my-custom-icon class to make it appear in one place:

.my-custom-icon {
    position: absolute;
    left: ...;
    right: ...;
}

3. Make use of overflow: hidden property:

<style> // don't add "scoped" attribute, otherwise the style won't be applied
.select .v-input__control {
  overflow: hidden;
}
</style>
like image 43
Cheetha Avatar answered Sep 18 '22 20:09

Cheetha