battleaxedotco / brutalism

Battleaxe's component library for Adobe CEP panels
https://battleaxe.dev/brutalism-docs/
MIT License
91 stars 12 forks source link

Toggle-Group to set Toggle Radio exclusively #38

Open vonnnassau opened 3 years ago

vonnnassau commented 3 years ago

A radiobutton is usually used in a group to make an exclusive option, the same way as buttons in <Button-Group exclusive> would behave. But there is no <Toggle-Group> for this.

As a workaround I wanted to set the states by script, but it seems a <toggle> always visually changes between true/false when being clicked on. I was expecting this toggle to visually stay false (radiobox-blank) while repeatedly clicking, but it keeps on switching between radiobox-blank and radiobox-marked while the value toggleState stays false.

<Toggle radio @click="toggleFalse" :state=toggleState @update="val => toggleState = false" />

<anno>{{toggleState}} </anno>

data: () => ({
    toggleState: false,
    }),

methods: {
    toggleFalse() {
    this.toggleState = false;
    }

Is there another workaround or would a <Toggle-Group> be useful because radiobuttons always perform with more than one in a group anyway? Many thanks!

Inventsable commented 3 years ago

Hey Erik, do you mean like this?

<Toggle> does accept v-model so assignment can be done relatively easily and has a readOnly boolean prop so that user interaction isn't considered. You can toggle the siblings either through a watcher or via the @update callback, but in the case of this screen recording, the entirety of the code is this (which is an App.vue file, though should be easy to abstract the logic from):

<template>
  <div id="app">
    <Menus refresh debug />
    <Panel>
      <Toggle
        radio
        v-model="stateA"
        label="Some option"
        @update="updateState($event, 'A')"
        :readOnly="stateA"
      />
      <Toggle
        radio
        v-model="stateB"
        label="Some option"
        @update="updateState($event, 'B')"
        :readOnly="stateB"
      />
      <Toggle
        radio
        v-model="stateC"
        label="Some option"
        @update="updateState($event, 'C')"
        :readOnly="stateC"
      />
    </Panel>
  </div>
</template>

<script>
export default {
  data: () => ({
    stateA: true,
    stateB: false,
    stateC: false,
  }),
  computed: {
    /**
     * If you find yourself needing an Array like [true, false, false], you could
     * use a watcher on this array and trigger some handler function so the data
     * is Array-based like Button-Group instead of singular like in this example
     */
    selection() {
      return ["A", "B", "C"]
        .map((id) => this[`state${id}`])
        .filter((val) => val);
    },
  },
  methods: {
    updateState(state, ID) {
      // First gather up all our targets
      const groupList = ["A", "B", "C"];
      // Filter out the id currently being toggled
      let siblings = groupList.filter((id) => id !== ID);

      // If this id has been toggled true, then set all siblings to false
      if (state)
        siblings.forEach((sibling) => {
          this[`state${sibling}`] = false;
        });
      else {
        /**
         * This should never trigger because Toggles are set to readOnly when true.
         * The reasoning behind this is to prevent a user from toggling the only true
         * radio button back to false, leaving us without any option selected.
         */
      }
    },
  },
};
</script>

Computed in the above isn't needed, just an example of an alternate way to collect the data in line with <Button-Group exclusive />.

While it's relatively easy to write out some manual mock of this, I'm not sure that it would be as simple to create a dynamic parent component like Toggle-Group. Or at least, I don't know until I try it, but generally it can be difficult to work with wrapper components having stateful children -- Button-Group isn't ideal because it has to comb through it's children's CSS classes to determine their states. I'll think about this more and we might be able to brainstorm a better solution together, but in the meantime let me know if this isn't what you meant.

Inventsable commented 3 years ago

Apparently the brutalism documentation doesn't include readOnly and v-model, apologies. In any case when there are requests or concerns with certain components like Toggle, feel free to peek at the code for that component because in many instances I'd added extra functions and props to them that I wasn't sure any one else would want to use or know about. I'll try to update the docs soon, but Github actions recently broke for me so it's no longer as simple as pushing a new commit.

vonnnassau commented 3 years ago

Hi Tom, thank you for this great example, this is the functionality I was looking for, very clear. No need for apologies at all, I now see it was all there. I'm peeking around vigorously, but there is a lot plus Vue is still new to me, so I got a little lost there.

As a solution I also tried a <button> in <button-group exclusive> and switch icon between radiobox-blank and radiobox-marked. Downside there was the backgroundcolor of the button get brighter on mouse-over, which a <toggle radio> does not. If a button has the option to not light up, then the radiobutton might be part of button instead of toggle. Lost the example, but can make it again if useful. For now I'm happy, many thanks for the help and all this brutalism.