diff --git a/packages/vuetify/src/components/VSwitch/VSwitch.sass b/packages/vuetify/src/components/VSwitch/VSwitch.sass index 3c81be92089..219af1772ec 100644 --- a/packages/vuetify/src/components/VSwitch/VSwitch.sass +++ b/packages/vuetify/src/components/VSwitch/VSwitch.sass @@ -80,7 +80,7 @@ .v-switch--inset & height: $switch-inset-thumb-height width: $switch-inset-thumb-width - transform: scale(calc($switch-inset-thumb-off-height / $switch-inset-thumb-height)) + transform: scale(var(--v-switch-inset-thumb-off-scale, #{$switch-inset-thumb-off-scale})) &--filled transform: none @@ -89,6 +89,68 @@ transform: none transition: .15s .05s transform settings.$decelerated-easing + .v-switch--inset-material + .v-switch__track + opacity: $switch-inset-track-opacity + background-color: $switch-inset-unselected-track-color + border: $switch-inset-border-width solid $switch-inset-unselected-thumb-color + + .v-switch__thumb + background-color: $switch-inset-unselected-thumb-color + color: $switch-inset-unselected-track-color + + .v-selection-control--dirty + .v-switch__track + border-color: transparent + background-color: $switch-inset-selected-track-color + + .v-switch__thumb + background-color: $switch-inset-selected-thumb-color + color: $switch-inset-selected-track-color + + .v-selection-control__input:active .v-switch__thumb + transform: scale(var(--v-switch-inset-thumb-pressed-scale, #{$switch-inset-thumb-pressed-scale})) + + .v-selection-control__input:before + --v-hover-opacity: 0.08 + z-index: 1 + + // focus is the track outline, so drop the overlay's focus state layer + .v-selection-control--focus-visible + .v-selection-control__input:before + opacity: 0 + + .v-switch__track + outline: $switch-focus-outline-width solid $switch-focus-outline-color + outline-offset: $switch-focus-outline-offset + + .v-selection-control--disabled + opacity: 1 + + .v-label + opacity: var(--v-disabled-opacity) + + .v-switch__track + background-color: $switch-inset-disabled-unselected-track-color + border-color: $switch-inset-disabled-unselected-border-color + + .v-switch__thumb + background-color: $switch-inset-disabled-unselected-thumb-color + + .v-icon + opacity: .8 + + .v-selection-control--disabled.v-selection-control--dirty + .v-switch__track + background-color: $switch-inset-disabled-selected-track-color + border-color: transparent + + .v-switch__thumb + background-color: $switch-inset-disabled-selected-thumb-color + + .v-icon + opacity: .4 + .v-switch $switch-thumb-transform: $switch-track-width * .5 - $switch-thumb-width * .5 + $switch-thumb-offset @@ -102,6 +164,9 @@ border-radius: 50% transition: $switch-control-input-transition position: absolute + height: calc(var(--v-switch-thumb-height) * 1.666666667) + width: calc(var(--v-switch-thumb-height) * 1.666666667) + @include tools.ltr() transform: translateX(-$switch-thumb-transform) @include tools.rtl() diff --git a/packages/vuetify/src/components/VSwitch/VSwitch.tsx b/packages/vuetify/src/components/VSwitch/VSwitch.tsx index 07432932784..f0942361d48 100644 --- a/packages/vuetify/src/components/VSwitch/VSwitch.tsx +++ b/packages/vuetify/src/components/VSwitch/VSwitch.tsx @@ -10,6 +10,7 @@ import { VProgressCircular } from '@/components/VProgressCircular' import { makeVSelectionControlProps, VSelectionControl } from '@/components/VSelectionControl/VSelectionControl' // Composables +import { useBackgroundColor } from '@/composables/color' import { useFocus } from '@/composables/focus' import { forwardRefs } from '@/composables/forwardRefs' import { LoaderSlot, useLoader } from '@/composables/loader' @@ -20,7 +21,7 @@ import { ref, toRef, useId } from 'vue' import { filterInputAttrs, genericComponent, propsFactory, SUPPORTS_MATCH_MEDIA, useRender } from '@/util' // Types -import type { ComputedRef, Ref } from 'vue' +import type { ComputedRef, PropType, Ref } from 'vue' import type { VInputSlots } from '@/components/VInput/VInput' import type { VSelectionControlSlots } from '@/components/VSelectionControl/VSelectionControl' import type { IconValue } from '@/composables/icons' @@ -43,8 +44,12 @@ export type VSwitchSlots = } export const makeVSwitchProps = propsFactory({ - inset: Boolean, + inset: { + type: [Boolean, String] as PropType, + default: false, + }, flat: Boolean, + thumbColor: String, loading: { type: [Boolean, String], default: false, @@ -78,6 +83,10 @@ export const VSwitch = genericComponent( const model = useProxiedModel(props, 'modelValue') const { loaderClasses } = useLoader(props) const { isFocused, focus, blur } = useFocus(props) + const { + backgroundColorClasses: thumbColorClasses, + backgroundColorStyles: thumbColorStyles, + } = useBackgroundColor(() => props.thumbColor) const control = ref() const inputRef = ref() const isForcedColorsModeActive = SUPPORTS_MATCH_MEDIA && window.matchMedia('(forced-colors: active)').matches @@ -106,6 +115,8 @@ export const VSwitch = genericComponent( const [rootAttrs, controlAttrs] = filterInputAttrs(attrs) const inputProps = VInput.filterProps(props) const controlProps = VSelectionControl.filterProps(props) + const isMaterial = ['material', 'square'].includes(String(props.inset)) + const hasThumbColor = !isForcedColorsModeActive && !!props.thumbColor return ( ( class={[ 'v-switch', { 'v-switch--flat': props.flat }, - { 'v-switch--inset': props.inset }, + { 'v-switch--inset': !!props.inset }, + { 'v-switch--inset-material': props.inset === 'material' }, { 'v-switch--indeterminate': indeterminate.value }, loaderClasses.value, props.class, @@ -179,23 +191,43 @@ export const VSwitch = genericComponent( )} ), - input: ({ inputNode, icon, backgroundColorClasses, backgroundColorStyles }) => ( + input: ({ + inputNode, + icon, + model: isSelected, + backgroundColorClasses, + backgroundColorStyles, + textColorClasses, + textColorStyles, + }) => ( <> { inputNode }
{ slots.thumb ? ( @@ -207,8 +239,10 @@ export const VSwitch = genericComponent( (icon && ( ))) : (