Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | 1x 2x 1x 1x 1x 1x 1x 1x 28x 21x 336x 180x 156x 108x 48x 7x 4x 4x 2x 2x 2x 4x 3x 2x 21x 1x 1x 1x 1x 1x 28x 28x 28x 112x 7x 224x 216x 1x 1x | <template>
<div class="cdx-toggle-button-group">
<cdx-toggle-button
v-for="button in buttons"
:key="button.value"
:model-value="isSelected( button )"
:disabled="button.disabled || disabled"
:aria-label="button.ariaLabel"
@update:model-value="onUpdate( button, $event )"
>
<!--
@slot Content of an individual button
@binding {ButtonGroupItem} button Object describing the button to display
@binding {boolean} selected Whether the button is selected
-->
<slot :button="button" :selected="isSelected( button )">
<cdx-icon v-if="button.icon" :icon="button.icon" />
{{ getButtonLabel( button ) }}
</slot>
</cdx-toggle-button>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { ButtonGroupItem } from '../../types';
import { getButtonLabel } from '../../utils/buttonHelpers';
import CdxIcon from '../icon/Icon.vue';
import CdxToggleButton from '../toggle-button/ToggleButton.vue';
/**
* A group of ToggleButtons. May be configured to allow for
* either single-select or multi-select behavior.
*/
export default defineComponent( {
name: 'CdxToggleButtonGroup',
components: {
CdxIcon,
CdxToggleButton
},
props: {
/**
* Buttons to display. See the ButtonGroupItem type.
*/
buttons: {
type: Array as PropType<ButtonGroupItem[]>,
required: true,
validator: ( value: unknown ) => Array.isArray( value ) && value.length >= 1
},
/**
* Selected value, or array of selected values.
*
* If this is a string or number, the button whose value equals that string or number is
* selected, and only a single selection is allowed. If this is an array, the buttons whose
* values equal any of the values in the array are selected, and multiple selections are
* allowed. To select none of the buttons initially, set this to `null`
* (for single-selection groups) or to `[]` (for multi-selection groups).
*
* Must be bound with `v-model`.
*/
modelValue: {
type: [ String, Number, null, Array ] as
PropType<string | number | null | ( string | number )[]>,
required: true
},
/**
* Whether the entire group is disabled.
*
* If this is set to true, all buttons in the group are disabled. Buttons can also be
* disabled individually by setting their `disabled` property to true.
*/
disabled: {
type: Boolean,
default: false
}
},
emits: [
/**
* Emitted when modelValue changes.
*
* @property {string | number | ( string | number )[]} modelValue The new model value
*/
'update:modelValue'
],
setup( props, E{ emit } ) {
function isSelected( button: ButtonGroupItem ): boolean {
if ( Array.isArray( props.modelValue ) ) {
return props.modelValue.indexOf( button.value ) !== -1;
} else if ( props.modelValue !== null ) {
return props.modelValue === button.value;
}
return false;
}
function onUpdate( button: ButtonGroupItem, nowSelected: boolean ) {
if ( Array.isArray( props.modelValue ) ) {
const wasSelected = props.modelValue.indexOf( button.value ) !== -1;
if ( nowSelected && !wasSelected ) {
// Add button.value to props.modelValue
emit( 'update:modelValue', props.modelValue.concat( button.value ) );
} else if ( !nowSelected && wasSelected ) {
// Remove button.value from props.modelValue
emit( 'update:modelValue', props.modelValue.filter( ( v ) => v !== button.value ) );
}
// The other combinations (nowSelected && wasSelected, !nowSelected && !wasSelected)
// should be impossible, and updating modelValue is not needed in those cases anyway
} else {
if ( nowSelected && props.modelValue !== button.value ) {
emit( 'update:modelValue', button.value );
}
// if !nowSelected, the user has clicked the selected button in a single-select
// ToggleButtonGroup. This should not lead to the button being unselected
// (just like with a radio button), so do nothing.
}
}
return {
getButtonLabel,
isSelected,
onUpdate
};
}
} );
</script>
<style lang="less">
@import ( reference ) '@wikimedia/codex-design-tokens/theme-wikimedia-ui.less';
@import ( reference ) '../../themes/mixins/button-group.less';
.cdx-toggle-button-group {
.cdx-mixin-button-group();
.cdx-toggle-button {
.cdx-mixin-button-group-button();
&--toggled-on:enabled {
// Make the borders of a toggled-on button appear above the borders of adjacent
// toggled-off buttons, but not above the borders of an active or focused button
// (those get `z-index: 3;` in 'button-group.less').
z-index: @z-index-stacking-2;
/* stylelint-disable stylistic/declaration-colon-newline-after,
stylistic/value-list-comma-newline-after */
// When two toggled-on buttons are adjacent to each other, display a white line
// between them, using the same box-shadow trick as in 'button-group.less'.
box-shadow: @box-shadow-outset-small-top @box-shadow-color-inverted,
@box-shadow-outset-small-start @box-shadow-color-inverted;
&:focus {
// Add a white box-shadow around the existing box-shadow applied to focused
// ToggleButtons. This is similar to the box-shadow above but slightly different:
// it appears on all four edges, not just the left and top edges.
box-shadow: @box-shadow-inset-small @box-shadow-color-progressive--focus,
@box-shadow-inset-medium @box-shadow-color-inverted,
@box-shadow-outset-small @box-shadow-color-inverted;
}
/* stylelint-enable stylistic/declaration-colon-newline-after,
stylistic/value-list-comma-newline-after */
}
}
}
</style>
|