-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Sergey
authored
May 5, 2022
1 parent
61fa5a7
commit 3f01f42
Showing
11 changed files
with
642 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { withInstall } from '../helpers'; | ||
|
||
import Switch from './src/QSwitch.vue'; | ||
|
||
export const QSwitch = withInstall(Switch); | ||
|
||
export type { | ||
QSwitchPropModelValue, | ||
QSwitchPropActiveValue, | ||
QSwitchPropInactiveValue, | ||
QSwitchProps | ||
} from './src/types'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
<template> | ||
<label | ||
class="q-switch" | ||
:class="classes" | ||
:tabindex="tabIndex" | ||
:aria-disabled="isDisabled" | ||
@keyup.enter="handleSwitcherChange" | ||
@keyup.space.prevent="handleSwitcherChange" | ||
@click.prevent="handleSwitcherChange" | ||
> | ||
<input | ||
class="q-switch__checkbox" | ||
type="checkbox" | ||
:checked="isChecked" | ||
tabindex="-1" | ||
/> | ||
<div class="q-switch__wrapper"> | ||
<div class="q-switch__handle"> | ||
<div | ||
v-if="loading" | ||
class="q-icon-reverse" | ||
/> | ||
</div> | ||
</div> | ||
</label> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent, computed, inject, watch } from 'vue'; | ||
import type { QFormItemProvider, QFormProvider } from '@/qComponents'; | ||
import type { ClassValue, Nullable } from '#/helpers'; | ||
import type { | ||
QSwitchProps, | ||
QSwitchInstance, | ||
QSwitchTabIndexType, | ||
QSwitchEmitValueType | ||
} from './types'; | ||
export default defineComponent({ | ||
name: 'QSwitch', | ||
componentName: 'QSwitch', | ||
props: { | ||
/** | ||
* default to v-model | ||
*/ | ||
modelValue: { | ||
type: [Boolean, String, Number], | ||
default: false | ||
}, | ||
/** | ||
* value for active QSwitch state | ||
*/ | ||
activeValue: { | ||
type: [Boolean, String, Number], | ||
default: true | ||
}, | ||
/** | ||
* value for inactive QSwitch state | ||
*/ | ||
inactiveValue: { | ||
type: [Boolean, String, Number], | ||
default: false | ||
}, | ||
/** | ||
* whether QSwitch is disabled | ||
*/ | ||
disabled: { | ||
type: Boolean, | ||
default: false | ||
}, | ||
/** | ||
* whether to show loader inside the QSwitch | ||
*/ | ||
loading: { | ||
type: Boolean, | ||
default: false | ||
}, | ||
/** | ||
* validate parent form if present | ||
*/ | ||
validateEvent: { | ||
type: Boolean, | ||
default: true | ||
} | ||
}, | ||
emits: [ | ||
/** | ||
* triggers when model updates | ||
*/ | ||
'update:modelValue', | ||
/** | ||
* alias for update:modelValue | ||
*/ | ||
'change' | ||
], | ||
setup(props: QSwitchProps, ctx): QSwitchInstance { | ||
const qFormItem = inject<Nullable<QFormItemProvider>>('qFormItem', null); | ||
const qForm = inject<Nullable<QFormProvider>>('qForm', null); | ||
const isChecked = computed<boolean>( | ||
() => props.modelValue === props.activeValue | ||
); | ||
const isDisabled = computed<boolean>( | ||
() => props.disabled || (qForm?.disabled.value ?? false) | ||
); | ||
const tabIndex = computed<QSwitchTabIndexType>(() => | ||
props.disabled ? -1 : 0 | ||
); | ||
const classes = computed<ClassValue>(() => ({ | ||
'q-switch_active': isChecked.value, | ||
'q-switch_disabled': isDisabled.value, | ||
'q-switch_loading': Boolean(props.loading) | ||
})); | ||
const emitChange = (value: QSwitchEmitValueType): void => { | ||
ctx.emit('update:modelValue', value); | ||
ctx.emit('change', value); | ||
}; | ||
const handleSwitcherChange = (): void => { | ||
if (props.disabled || props.loading) return; | ||
const value = isChecked.value ? props.inactiveValue : props.activeValue; | ||
emitChange(value); | ||
}; | ||
watch( | ||
() => props.modelValue, | ||
() => { | ||
if (props.validateEvent) qFormItem?.validateField('change'); | ||
} | ||
); | ||
return { | ||
isChecked, | ||
tabIndex, | ||
classes, | ||
isDisabled, | ||
handleSwitcherChange | ||
}; | ||
} | ||
}); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
.q-switch { | ||
--switch-main-color: var(--color-primary); | ||
--switch-handle-width: 24px; | ||
--switch-wrapper-translate-x: calc(-100% + var(--switch-handle-width)); | ||
|
||
position: relative; | ||
display: block; | ||
width: 40px; | ||
overflow: hidden; | ||
cursor: pointer; | ||
background: var(--color-tertiary-gray-ultra-light); | ||
border-radius: 12px; | ||
box-shadow: 4px 4px 8px rgb(174 174 192 / 40%), | ||
1px 1px 3px rgb(174 174 192 / 40%); | ||
|
||
&__checkbox { | ||
position: absolute; | ||
top: 0; | ||
right: 0; | ||
bottom: 0; | ||
left: 0; | ||
z-index: -1; | ||
margin: 0; | ||
outline: none; | ||
opacity: 0; | ||
} | ||
|
||
&__wrapper { | ||
width: 100%; | ||
background-color: var(--switch-main-color); | ||
border-radius: 12px; | ||
transition: transform 0.2s ease-out; | ||
transform: translateX(var(--switch-wrapper-translate-x)); | ||
} | ||
|
||
&__handle { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
width: var(--switch-handle-width); | ||
height: 24px; | ||
margin-left: auto; | ||
background-color: var(--color-tertiary-gray-ultra-light); | ||
border: 2px solid var(--switch-main-color); | ||
border-radius: 12px; | ||
transition: width 0.2s ease-out; | ||
|
||
.q-icon-reverse { | ||
font-size: 16px; | ||
color: var(--switch-main-color); | ||
animation: rotating 2s linear infinite; | ||
} | ||
} | ||
|
||
&_disabled { | ||
--switch-main-color: var(--color-tertiary-gray-ultra-darker); | ||
|
||
cursor: not-allowed; | ||
box-shadow: var(--box-shadow-pressed); | ||
} | ||
|
||
&_active { | ||
--switch-wrapper-translate-x: 0; | ||
|
||
&:active:not(.q-switch_disabled, .q-switch_loading) { | ||
--switch-handle-width: 28px; | ||
} | ||
} | ||
|
||
&_loading { | ||
cursor: progress; | ||
} | ||
|
||
&.focus-visible { | ||
outline: none; | ||
} | ||
|
||
&:active:not(&_disabled, &_loading, &_active) { | ||
--switch-handle-width: 28px; | ||
} | ||
|
||
&:focus-visible:not(&_disabled, &_loading), | ||
&.focus-visible:not(&_disabled, &_loading) { | ||
--switch-main-color: var(--color-primary-darker); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import type { ComputedRef } from 'vue'; | ||
import type { Nullable, ClassValue } from '#/helpers'; | ||
|
||
export type QSwitchPropModelValue = Nullable<boolean | string | number>; | ||
export type QSwitchPropActiveValue = QSwitchPropModelValue; | ||
export type QSwitchPropInactiveValue = QSwitchPropActiveValue; | ||
|
||
export type QSwitchTabIndexType = -1 | 0; | ||
|
||
export type QSwitchEmitValueType = | ||
| QSwitchPropInactiveValue | ||
| QSwitchPropActiveValue; | ||
|
||
export interface QSwitchProps { | ||
modelValue: QSwitchPropModelValue; | ||
activeValue: QSwitchPropActiveValue; | ||
inactiveValue: QSwitchPropInactiveValue; | ||
disabled: Nullable<boolean>; | ||
loading: Nullable<boolean>; | ||
validateEvent: Nullable<boolean>; | ||
} | ||
|
||
export interface QSwitchInstance { | ||
classes: ComputedRef<ClassValue>; | ||
isChecked: ComputedRef<boolean>; | ||
tabIndex: ComputedRef<number>; | ||
isDisabled: ComputedRef<boolean>; | ||
handleSwitcherChange: () => void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import type { Meta, Story } from '@storybook/vue3'; | ||
import { defineComponent, ref } from 'vue'; | ||
|
||
import { QSwitch } from '@/qComponents'; | ||
import type { QSwitchProps } from '@/qComponents'; | ||
|
||
const storyMetadata: Meta = { | ||
title: 'Components/QSwitch', | ||
component: QSwitch, | ||
|
||
argTypes: { | ||
modelValue: { control: { type: 'none' } }, | ||
validateEvent: { control: { type: 'none' } }, | ||
activeValue: { control: { type: 'text' } }, | ||
inactiveValue: { control: { type: 'text' } } | ||
} | ||
}; | ||
|
||
const QSwitchStory: Story<QSwitchProps> = args => | ||
defineComponent({ | ||
setup() { | ||
const isOn = ref(true); | ||
|
||
return { | ||
args, | ||
isOn | ||
}; | ||
}, | ||
|
||
template: ` | ||
<q-switch | ||
v-model="isOn" | ||
:loading="args.loading" | ||
:disabled="args.disabled" | ||
:active-value="args.activeValue" | ||
:inactive-value="args.inactiveValue" | ||
/> | ||
` | ||
}); | ||
|
||
export const Default = QSwitchStory.bind({}); | ||
export default storyMetadata; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.