src/components/base/toggle/toggle.vue (186 lines of code) (raw):

<!-- eslint-disable vue/multi-word-component-names --> <script> import uniqueId from 'lodash/uniqueId'; import { toggleLabelPosition } from '../../../utils/constants'; import GlIcon from '../icon/icon.vue'; import GlLoadingIcon from '../loading_icon/loading_icon.vue'; let uuid = 0; export default { name: 'GlToggle', components: { GlIcon, GlLoadingIcon, }, model: { prop: 'value', event: 'change', }, props: { name: { type: String, required: false, default: null, }, /** * The toggle's state. * @model */ value: { type: Boolean, required: false, default: null, }, /** * Whether the toggle should be disabled. */ disabled: { type: Boolean, required: false, default: false, }, /** * Whether the toggle is in the loading state. */ isLoading: { type: Boolean, required: false, default: false, }, /** * The toggle's label. */ label: { type: String, required: false, default: null, }, /** * The toggle's description. */ description: { type: String, required: false, default: undefined, }, /** * A help text to be shown below the toggle. */ help: { type: String, required: false, default: undefined, }, /** * The label's position relative to the toggle. If 'hidden', the toggle will add the .gl-sr-only class so the label is still accessible to screen readers. */ labelPosition: { type: String, required: false, default: 'top', validator(position) { return Object.values(toggleLabelPosition).includes(position); }, }, }, data() { return { labelId: uniqueId('toggle-label-'), }; }, computed: { shouldRenderDescription() { // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots return Boolean(this.$scopedSlots.description || this.description) && this.isVerticalLayout; }, shouldRenderHelp() { // eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots return Boolean(this.$slots.help || this.help) && this.isVerticalLayout; }, toggleClasses() { return [ { 'gl-sr-only': this.labelPosition === 'hidden' }, this.shouldRenderDescription ? 'gl-mb-2' : 'gl-mb-3', ]; }, icon() { return this.value ? 'check-xs' : 'close-xs'; }, helpId() { return this.shouldRenderHelp ? `toggle-help-${this.uuid}` : undefined; }, isChecked() { return this.value ? 'true' : 'false'; }, isVerticalLayout() { return this.labelPosition === 'top' || this.labelPosition === 'hidden'; }, }, beforeCreate() { this.uuid = uuid; uuid += 1; }, methods: { toggleFeature() { if (!this.disabled) { /** * Emitted when the state changes. * * @event change * @property {boolean} value Whether the toggle is enabled. */ this.$emit('change', !this.value); } }, }, }; </script> <template> <div class="gl-toggle-wrapper gl-mb-0 gl-flex" :class="{ 'gl-flex-col': isVerticalLayout, 'gl-toggle-label-inline': !isVerticalLayout, 'is-disabled': disabled, }" data-testid="toggle-wrapper" > <span :id="labelId" :class="toggleClasses" class="gl-toggle-label gl-shrink-0" data-testid="toggle-label" > <!-- @slot The toggle's label. --> <slot name="label">{{ label }}</slot> </span> <span v-if="shouldRenderDescription" class="gl-description-label gl-mb-3" data-testid="toggle-description" > <!-- @slot A description text to be shown below the label. --> <slot name="description">{{ description }}</slot> </span> <input v-if="name" :name="name" :value="value" type="hidden" /> <button role="switch" :aria-checked="isChecked" :aria-labelledby="labelId" :aria-describedby="helpId" :aria-disabled="disabled" :class="{ 'is-checked': value, 'is-disabled': disabled || isLoading, }" class="gl-toggle gl-shrink-0" type="button" :disabled="disabled" @click.prevent="toggleFeature" > <gl-loading-icon v-if="isLoading" color="dark" class="toggle-loading" /> <span v-else class="toggle-icon"> <gl-icon :name="icon" :size="12" /> </span> </button> <span v-if="shouldRenderHelp" :id="helpId" class="gl-help-label" data-testid="toggle-help"> <!-- @slot A help text to be shown below the toggle. --> <slot name="help">{{ help }}</slot> </span> </div> </template>