Skip to content

Commit b4d47e1

Browse files
committed
feat: NcFormBoxSwitch
Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
1 parent b0a5563 commit b4d47e1

3 files changed

Lines changed: 162 additions & 0 deletions

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<script setup lang="ts">
7+
import type { Slot } from 'vue'
8+
9+
import { mdiToggleSwitch, mdiToggleSwitchOff } from '@mdi/js'
10+
import { watch } from 'vue'
11+
import NcFormBoxItem from '../NcFormBox/NcFormBoxItem.vue'
12+
import NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue'
13+
import { createElementId } from '../../utils/createElementId.ts'
14+
15+
/** Switch toggle model value */
16+
const modelValue = defineModel<boolean>({ required: true })
17+
18+
const {
19+
label = undefined,
20+
description = undefined,
21+
disabled = false,
22+
} = defineProps<{
23+
/** Main label */
24+
label?: string
25+
/** Optional description below the label, also used for the aria-describedby */
26+
description?: string
27+
/** Disabled state */
28+
disabled?: boolean
29+
}>()
30+
31+
const emit = defineEmits<{
32+
/** Switch is toggled ON */
33+
enable: []
34+
/** Switch is toggled OFF */
35+
disable: []
36+
}>()
37+
38+
defineSlots<{
39+
/** Custom label content */
40+
default?: Slot
41+
/** Custom description content */
42+
description?: Slot
43+
}>()
44+
45+
const inputId = createElementId()
46+
47+
watch(modelValue, () => {
48+
if (modelValue.value) {
49+
emit('enable')
50+
} else {
51+
emit('disable')
52+
}
53+
}, {
54+
// defineModel emits update:modelValue synchronously
55+
// Watching it synchronously to emit the enable/disable events together with the update:modelValue event
56+
flush: 'sync',
57+
})
58+
</script>
59+
60+
<template>
61+
<NcFormBoxItem
62+
tag="label"
63+
:for="inputId">
64+
<template v-if="$slots.default || label" #default>
65+
<slot>
66+
{{ label }}
67+
</slot>
68+
</template>
69+
<template v-if="$slots.description || description" #description>
70+
<slot name="description">
71+
{{ description }}
72+
</slot>
73+
</template>
74+
<template #icon="{ descriptionId }">
75+
<input
76+
:id="inputId"
77+
v-model="modelValue"
78+
:class="$style.formBoxSwitch__input"
79+
type="checkbox"
80+
role="switch"
81+
:aria-describedby="descriptionId"
82+
:disabled>
83+
<NcIconSvgWrapper
84+
:path="modelValue ? mdiToggleSwitch : mdiToggleSwitchOff"
85+
:class="$style.formBoxSwitch__icon"
86+
:size="34 /* --default-clickable-area */"
87+
inline />
88+
</template>
89+
</NcFormBoxItem>
90+
</template>
91+
92+
<style lang="scss" module>
93+
input.formBoxSwitch__input {
94+
margin: 0;
95+
width: var(--default-clickable-area);
96+
/* Keep it visually hidden but on the position of visual switch icon */
97+
position: absolute;
98+
inset-block: 0;
99+
inset-inline-end: var(--form-element-label-offset);
100+
z-index: -1;
101+
opacity: 0 !important;
102+
/* Override server styles */
103+
height: auto;
104+
cursor: inherit;
105+
}
106+
107+
.formBoxSwitch__icon {
108+
color: var(--color-text-maxcontrast);
109+
}
110+
111+
input:checked + .formBoxSwitch__icon {
112+
color: var(--color-primary-element);
113+
}
114+
</style>
115+
116+
<docs>
117+
### General
118+
119+
A toggle switch to be used within `<NcFormBox>`.
120+
121+
Like other form box items, it has a label and an optional description.
122+
123+
In addition to the standard `v-model` binding, it emits `enable` and `disable` events when toggled **on** or **off**.
124+
125+
```vue
126+
<script>
127+
export default {
128+
data() {
129+
return {
130+
switchValue: false,
131+
}
132+
}
133+
}
134+
</script>
135+
136+
<template>
137+
<NcFormBox>
138+
<NcFormBoxSwitch v-model="switchValue" label="Turn camera and microphone off by default" />
139+
<NcFormBoxSwitch
140+
v-model="switchValue"
141+
label="Blur camera background by default"
142+
disabled />
143+
<NcFormBoxSwitch
144+
v-model="switchValue"
145+
label="Skip device preview before joining a call"
146+
description="Will always show if recording consent is required"
147+
@update:modelValue="console.log('Switch toggled', $event, switchValue)"
148+
@enable="console.log('Switch enabled', switchValue)"
149+
@disable="console.log('Switch disabled', switchValue)"
150+
/>
151+
<NcFormBoxSwitch v-model="switchValue" />
152+
</NcFormBox>
153+
</template>
154+
```
155+
</docs>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
export { default } from './NcFormBoxSwitch.vue'

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export { default as NcEmojiPicker } from './NcEmojiPicker/index.js'
6060
export { default as NcEmptyContent } from './NcEmptyContent/index.ts'
6161
export { default as NcFormBox } from './NcFormBox/index.ts'
6262
export { default as NcFormBoxButton } from './NcFormBoxButton/index.ts'
63+
export { default as NcFormBoxSwitch } from './NcFormBoxSwitch/index.ts'
6364
export { default as NcFormGroup } from './NcFormGroup/index.ts'
6465
export { default as NcGuestContent } from './NcGuestContent/index.ts'
6566
export { default as NcHeaderButton } from './NcHeaderButton/index.ts'

0 commit comments

Comments
 (0)