Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/components/NcButton/NcButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ import type { RouteLocationRaw } from 'vue-router'

import { computed, inject } from 'vue'
import { routerKey } from 'vue-router'
import { FIELDSET_CONTAINER_CLASS_KEY } from '../NcFieldset/constants'

export type ButtonAlignment = 'start'
| 'start-reverse'
Expand Down Expand Up @@ -601,6 +602,7 @@ defineSlots<{
icon?: Slot
}>()

const fieldsetClass = inject(FIELDSET_CONTAINER_CLASS_KEY, '')
const hasVueRouterContext = inject(routerKey, null) !== null

const tag = computed(() => {
Expand Down Expand Up @@ -680,6 +682,7 @@ function onClick(event: MouseEvent) {
<component :is="tag"
class="button-vue"
:class="[
fieldsetClass,
`button-vue--size-${size}`,
{
[`button-vue--${variantWithPressed}`]: variantWithPressed,
Expand Down Expand Up @@ -719,7 +722,7 @@ function onClick(event: MouseEvent) {
background-color: var(--color-primary-element-light);
border: 1px solid var(--color-primary-element-light-hover);
border-bottom-width: 2px;
border-radius: var(--button-radius);
border-radius: var(--component-border-radius, var(--button-radius));
box-sizing: border-box;
// adjust position and size
position: relative;
Expand Down
2 changes: 0 additions & 2 deletions src/components/NcCheckboxRadioSwitch/NcCheckboxContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,6 @@ export default {
padding: var(--default-grid-baseline) calc((var(--default-clickable-area) - var(--icon-height)) / 2);
// Set to 100% to make text overflow work on button style
width: 100%;
// but restrict to content so plain checkboxes / radio switches do not expand
max-width: fit-content;

&__text {
flex: 1 0;
Expand Down
17 changes: 16 additions & 1 deletion src/components/NcCheckboxRadioSwitch/NcCheckboxRadioSwitch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@
class="checkbox-radio-switch"
:class="[
$props.class,
fieldsetContainerClass,
{
['checkbox-radio-switch-' + type]: type,
'checkbox-radio-switch--checked': isChecked,
Expand All @@ -265,6 +266,7 @@
'checkbox-radio-switch--button-variant': buttonVariant,
'checkbox-radio-switch--button-variant-v-grouped': buttonVariant && buttonVariantGrouped === 'vertical',
'checkbox-radio-switch--button-variant-h-grouped': buttonVariant && buttonVariantGrouped === 'horizontal',
'checkbox-radio-switch--in-fieldset': !!fieldsetContainerClass,
'button-vue': isButtonType,
},
]"
Expand Down Expand Up @@ -314,6 +316,7 @@
import NcCheckboxContent, { TYPE_BUTTON, TYPE_CHECKBOX, TYPE_RADIO, TYPE_SWITCH } from './NcCheckboxContent.vue'
import { createElementId } from '../../utils/createElementId.ts'
import { t, n } from '../../l10n.ts'
import { FIELDSET_CONTAINER_CLASS_KEY } from '../NcFieldset/constants.ts'

export default {
name: 'NcCheckboxRadioSwitch',
Expand Down Expand Up @@ -479,6 +482,13 @@

emits: ['update:modelValue'],

inject: {

Check warning on line 485 in src/components/NcCheckboxRadioSwitch/NcCheckboxRadioSwitch.vue

View workflow job for this annotation

GitHub Actions / eslint

The "inject" property should be above the "inheritAttrs" property on line 329
fieldsetContainerClass: {
from: FIELDSET_CONTAINER_CLASS_KEY,
default: '',
}

Check warning on line 489 in src/components/NcCheckboxRadioSwitch/NcCheckboxRadioSwitch.vue

View workflow job for this annotation

GitHub Actions / eslint

Missing trailing comma
},

computed: {
isButtonType() {
return this.type === TYPE_BUTTON
Expand Down Expand Up @@ -637,7 +647,7 @@
.checkbox-radio-switch {
--icon-size: v-bind('cssIconSize');
--icon-height: v-bind('cssIconHeight');
--checkbox-radio-switch--border-radius: var(--border-radius-element);
--checkbox-radio-switch--border-radius: var(--component-border-radius, var(--border-radius-element));
// keep inner border width in mind
--checkbox-radio-switch--border-radius-outer: calc(var(--checkbox-radio-switch--border-radius) + 2px);
// general setup
Expand All @@ -649,6 +659,7 @@
line-height: var(--default-line-height);
padding: 0;
position: relative;
width: fit-content;

&__input {
position: absolute;
Expand Down Expand Up @@ -677,6 +688,10 @@
}
}

&--in-fieldset &__content {
background-color: var(--color-primary-element-light);
}

&:not(&--disabled, &--checked):focus-within &__content,
&:not(&--disabled, &--checked) &__content:hover {
background-color: var(--color-background-hover);
Expand Down
177 changes: 177 additions & 0 deletions src/components/NcFieldset/NcFieldset.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<docs>
### Usage

The most common usage would be to group input elements and ensuring consistent design within the `NcAppSettingsSection`:

```vue
<template>
<div class="wrapper">
<NcFieldset
label="Buttons"
description="This example shows that the inner button has no border radius but the first and the last have on the outer side.">
<NcButton>First button</NcButton>
<NcButton>Second button</NcButton>
<NcButton>Third button</NcButton>
</NcFieldset>

<NcFieldset
label="Switches"
description="Grouping multiple on-off options is of course also possible. Using the switch style is recommended for such settings.">
<NcCheckboxRadioSwitch :model-value="switch1">Nice feature</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch :model-value="switch2">Super enhancement</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch :model-value="switch3">Optional</NcCheckboxRadioSwitch>
</NcFieldset>

<NcFieldset
label="Mixed"
description="Not only are also links possible, but also other options using the NcFormsField.">

Check failure on line 33 in src/components/NcFieldset/NcFieldset.vue

View workflow job for this annotation

GitHub Actions / eslint

Trailing spaces not allowed
</NcFieldset>
</div>
</template>

<script>
import { mdiLockCheck } from '@mdi/js'

export default {
setup() {
return {
mdiLockCheck,
}
},
data() {
return {
switch1: true,
switch2: true,
switch3: false,
}
},
}
</script>

<style scoped>
.wrapper {
max-width: 600px;
}
</style>
```
</docs>

<script setup lang="ts">
import type { Slot } from 'vue'

import { inject, provide, useCssModule, warn, watchEffect} from 'vue'

Check failure on line 68 in src/components/NcFieldset/NcFieldset.vue

View workflow job for this annotation

GitHub Actions / eslint

A space is required before '}'
import { FIELDSET_CONTAINER_CLASS_KEY } from './constants'

const props = defineProps<{
/**
* The id of an element which labels this fieldset.
* For accessibility reasons either this reference must be given or a visual `label` must be set.
*/
ariaLabelledby?: string

/**
* The visual label of this fieldset.
* If no visual label is used an implicit label using the `ariaLabelledby` must be set.
*/
label?: string

/**
* Optional visual description of this fieldset.
*/
description?: string
}>()

defineSlots<{
/**
* Slot for the main content - the form elements - of this fieldset.
*/
default?: Slot

/**
* Optional custom formatted description.
* This will take predecence over the `description` prop.
*/
description?: Slot
}>()

const cssClasses = useCssModule()
const parentFieldsetClass = inject(FIELDSET_CONTAINER_CLASS_KEY, '')
provide(FIELDSET_CONTAINER_CLASS_KEY, cssClasses.child)

watchEffect(() => {
if (!props.ariaLabelledby && !props.label) {
warn('NcFieldset needs either the `label` or `ariaLabelledby` prop set for accessibility.')
} else if (props.ariaLabelledby && !document.getElementById(props.ariaLabelledby)) {
warn('NcFieldset: The element set for `ariaLabelledby` does not exist on the page.')
}
})
</script>

<template>
<fieldset :aria-labelledby :class="$style.fieldset">
<legend v-if="label" :class="[$style.label, parentFieldsetClass ? $style.nestedLabel : '']">
{{ label }}
</legend>
<p :class="$style.description">
<slot name="description">{{ description }}</slot>

Check warning on line 122 in src/components/NcFieldset/NcFieldset.vue

View workflow job for this annotation

GitHub Actions / eslint

Expected 1 line break before closing tag (`</slot>`), but no line breaks found

Check warning on line 122 in src/components/NcFieldset/NcFieldset.vue

View workflow job for this annotation

GitHub Actions / eslint

Expected 1 line break after opening tag (`<slot>`), but no line breaks found
</p>
<div :class="$style.content">
<slot />
</div>
</fieldset>
</template>

<style module lang="scss">
.fieldset {
margin-top: var(--default-grid-baseline);
}

.label {
color: var(--color-main-text);
font-size: 1.2em;
font-weight: 600;
text-align: start;
margin-inline-start: var(--border-radius-element);
width: 100%;
}

.nestedLabel {
font-size: var(--default-font-size);
}

.description {
color: var(--color-text-maxcontrast);
margin-inline-start: var(--border-radius-element);
margin-bottom: calc(var(--default-grid-baseline) / 2);

&:empty {
display: none;
}
}

.content {
display: flex;
flex-direction: column;
gap: var(--default-grid-baseline);
width: 100%;
}

.child {
--component-border-radius: 0;
border-radius: var(--component-border-radius) !important;
width: 100% !important;

&:first-of-type {
--component-border-radius: var(--border-radius-element) var(--border-radius-element) 0 0;
}
&:last-of-type {
--component-border-radius: 0 0 var(--border-radius-element) var(--border-radius-element);
}
}
</style>
8 changes: 8 additions & 0 deletions src/components/NcFieldset/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*!
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { InjectionKey } from 'vue'

export const FIELDSET_CONTAINER_CLASS_KEY: InjectionKey<string> = Symbol.for('NcFieldset:container-class')

Check failure on line 8 in src/components/NcFieldset/constants.ts

View workflow job for this annotation

GitHub Actions / eslint

Newline required at end of file but not found
6 changes: 6 additions & 0 deletions src/components/NcFieldset/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

export { default } from './NcFieldset.vue'

Check failure on line 6 in src/components/NcFieldset/index.ts

View workflow job for this annotation

GitHub Actions / eslint

Newline required at end of file but not found
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export { default as NcDialogButton } from './NcDialogButton/index.ts'
export { default as NcEllipsisedOption } from './NcEllipsisedOption/index.js'
export { default as NcEmojiPicker } from './NcEmojiPicker/index.js'
export { default as NcEmptyContent } from './NcEmptyContent/index.ts'
export { default as NcFieldset } from './NcFieldset/index.ts'
export { default as NcGuestContent } from './NcGuestContent/index.ts'
export { default as NcHeaderButton } from './NcHeaderButton/index.ts'
export { default as NcHeaderMenu } from './NcHeaderMenu/index.ts'
Expand Down
Loading