3

so I am trying to achieve the following design: enter image description here

Specifically the colourful badges on top. Now these items are grouped and can be any number. In the picture they are grouped into 2 but they can easily be 4 or 5.

I wanted a way to programmatically change the background and text colour of each badge for each group.

I have tried soo many things that haven't worked for me, at best I am currently only able to get the first colour to change.

this is my page:

<template>
  <div class="w-full flex flex-col px-6">
    <div class="flex flex-row">
      <button class="flex flex-wrap justify-center content-center w-20 h-20 bg-blob-1 bg-no-repeat bg-contain bg-center -ml-3">
        <img class="flex w-5 h-5" src="~/assets/images/icon-back.svg" alt="">
      </button>
    </div>

    <div class="flex flex-col mt-1">
      <span class="font-raleway font-black text-2xl text-white">{{ route.origin }} - {{ route.destination }}</span>
      <div class="inline-block w-44 bg-black bg-opacity-50 rounded p-2">
         <div class="font-raleway text-md text-white">{{ departureDate.formattedDate }}</div>
      </div>
    </div>

    <div class="flex flex-col mt-10">
      <type :name="name" :bg-color="colours[index].bg" :text-color="colours[index].text" v-for="(values, name, index) in schedules" :key="name" class="mb-10">
        <schedule :schedule="schedule" v-for="schedule in values" :key="schedule.id" class="mb-1" />
      </type>
    </div>
  </div>
</template>

<script>
import { mapState } from 'vuex';
import type from '~/components/bus/type.vue';
import schedule from '~/components/bus/schedule.vue';

export default {
  name: 'schedules',
  layout: 'bus-default',
  components: {type, schedule},
  async fetch({ store }) {
    const trip = store.state.trip;
    const schedule = store.state.schedule;

    await store.dispatch('schedule/getSchedules', {
      company: trip.company.alias,
      origin: trip.route.origin,
      destination: trip.route.destination, 
      date: trip.departureDate.fullDate,
      schedules: schedule.schedules
    });
  },
  computed: {
    ...mapState({
        company: state => state.trip.company.name,
        route: state => state.trip.route,
        departureDate: state => state.trip.departureDate,
        schedules: state => state.schedule.schedules,
        colours: state => state.schedule.colours
    }),
  }
}
</script>

This is my component that contains the badge:

<template>
  <div>
      <div :class="{bgColor: true, textColor}" :key="bgColor" class="w-auto inline-block rounded-full px-3 py-1 ml-3 absolute z-20 shadow-md -mt-4 font-raleway text-sm capitalize">{{ name }}</div>
      <slot></slot>
  </div>
</template>

<script>
import { mapState } from 'vuex';

export default {
  name: 'type',
  props: ['name', 'bg-color', 'text-color'],
  computed: {
    ...mapState({
        colours: state => state.schedule.colours
    }),
  }
}
</script>

This is my store file:

export const state = () => ({
    schedules: [],
    schedule: {},
    colours: [{'bg': 'bg-red-400', 'text': 'text-white'}, {'bg': 'bg-blue-400', 'text': 'text-white'}, {'bg': 'bg-yellow-600', 'text': 'text-gray-800'}],
    colour: {}
});

export const mutations = {
    setColours(state, colours) {
        state.colours = colours;
    },
    setColour(state, colour) {
        state.colour = colour;
    },
    setSchedules(state, schedules) {
        state.schedules = schedules;
    },
}

export const actions = {
    async getSchedules({ commit }, params) {
        const res = await this.$api.get('/bus/schedules', { params: params });
        commit('setSchedules', res.data);    
    },
    initialiseColours({ commit }) {
        const colours = [{'bg': 'bg-red-400', 'text': 'text-white'}, {'bg': 'bg-blue-400', 'text': 'text-white'}, {'bg': 'bg-yellow-600', 'text': 'text-gray-800'}];
        commit('setColours', colours);  
    },
    getRandomColour({ commit, state }) {
        var colours = [...state.colours];
         console.log('colours:', colours);
        var index = Math.floor(Math.random() * colours.length);
        const colour = colours.splice(index, 1)[0];
        commit('setColours', colours); 
        commit('setColour', colour);    
    },
}

So what I want to achieve here is to programmatically assign random background colours to each "badge" in each group. The badges I'm referring to are the executive and standard in the picture.

Also the text should be visible depending on the background, white when necessary or black when necessary.

For some reason my solution only changes the first item, the 2nd item is transparent however when I inspect HTML I see the class there but it doesn't show the colour in the browser.

Edit: So one last thing I forgot to add is colours should be used without replacement, meaning when one could has been used it should not repeat again.

kissu
  • 40,416
  • 14
  • 65
  • 133
user3718908x100
  • 7,939
  • 15
  • 64
  • 123
  • Do you really need to use vuex here? Looks like more work than anything else. Otherwise, this is my take on your use case: https://stackoverflow.com/a/67382023/8816585 If you want something totally random, use a simple `colours` array with `['bg-red-400', 'bg-blue-400', 'bg-yellow-400' etc...]` in it rather than an array of objects with a useless `bg` key. – kissu May 12 '21 at 23:48

2 Answers2

3

This is a quick and simple solution without Vuex. Should probably work as well tho, if you really think that you need something global.

<template>
  <div>
    <div
      v-for="(button, index) in numberOfIterations"
      :key="button"
      :class="[arrayOfColours[findRandomInRange()]]"
    >
      div #{{ index }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arrayOfColours: ['red', 'blue', 'orange'],
      numberOfIterations: 6,
    }
  },
  methods: {
    findRandomInRange() {
      return Math.floor(Math.random() * this.numberOfIterations) % this.arrayOfColours.length
    },
  },
}
</script>

<style>
.red {
  background-color: red;
}
.blue {
  background-color: blue;
}
.orange {
  background-color: orange;
}
</style>

Explanations:

  • numberOfIterations is just used to have a basic loop
  • % this.arrayOfColours.length is given to have boundaries within our colors. In my example, I set 6 iterations but have only 3 colors, would be annoying if we were trying to get the 5th colour for example, since there are only 3 items in the array
  • findRandomInRange() is invoked during rendering since you probably want them to be colored on render
  • the :key could clearly be something better in this case
  • in my example, the colors are of course shuffled on each div, each time you do render them again

I hope the rest is self-explanatory.

And here is how it looks enter image description here


EDIT after I saw that you want to have unique colors, but still have some random. The code is even shorter.

<template>
  <div>
    <div v-for="(button, index) in arrayOfColours" :key="button" :class="[arrayOfColours[index]]">
      div #{{ index }}
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arrayOfColours: ['red', 'blue', 'orange'],
    }
  },
  created() {
    this.shuffle() // shuffle the order of the array before you mount it to the DOM
  },
  methods: {
    shuffle() {
      this.arrayOfColours.sort(() => Math.random() - 0.5)
    },
  },
}
</script>
kissu
  • 40,416
  • 14
  • 65
  • 133
3

Your code is close to working, but there's a problem in the class binding:

<div :class="{bgColor: true, textColor}"> ❌

That binding sets two classes on the div named "bgColor" and "textColor", but if you actually want the values of those props to be the class names, you should bind an array of those props:

<div :class="[bgColor, textColor]">

Assuming those class names correspond to existing styles, the background and text color would update accordingly.

To randomize the badge colors, shuffle a copy of the state.schedule.colours array in the computed property:

// https://stackoverflow.com/a/2450976/6277151
function shuffleArray(array) {
  if (!array || array.length <= 1) return array;
  array = array.slice();
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    const temp = array[i];
    array[i] = array[j];
    array[j] = temp;
  }
  return array;
}

export default {
  computed: {
    ...mapState({
      colours: state => shuffleArray(state.schedule.colours)
    }),
  }
}

demo

tony19
  • 125,647
  • 18
  • 229
  • 307