Skip to content

Commit bdd7e5a

Browse files
ShGKmebackportbot[bot]
authored andcommitted
feat: add NcFormGroup
Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
1 parent d7e3718 commit bdd7e5a

File tree

3 files changed

+192
-0
lines changed

3 files changed

+192
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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 { provide, useCssModule } from 'vue'
10+
import { NC_FORM_BOX_CONTEXT_KEY } from './useNcFormBox.ts'
11+
12+
defineProps<{
13+
/**
14+
* Display the group as a row instead of a column
15+
*/
16+
row?: boolean
17+
}>()
18+
19+
defineSlots<{
20+
/**
21+
* Grouped content
22+
*/
23+
default?: Slot<{
24+
/**
25+
* Class to add on a custom item to apply the border radius effect
26+
*/
27+
itemClass: string
28+
}>
29+
}>()
30+
31+
const style = useCssModule()
32+
33+
provide(NC_FORM_BOX_CONTEXT_KEY, {
34+
isInFormBox: true,
35+
formBoxItemClass: style.ncFormBox__item,
36+
})
37+
</script>
38+
39+
<template>
40+
<div :class="[$style.ncFormBox, row ? $style.ncFormBox_row : $style.ncFormBox_col]">
41+
<slot :item-class="$style.ncFormBox__item" />
42+
</div>
43+
</template>
44+
45+
<style lang="scss" module>
46+
.ncFormBox {
47+
display: flex;
48+
flex-direction: column;
49+
gap: calc(1 * var(--default-grid-baseline));
50+
51+
&.ncFormBox_row {
52+
flex-direction: row;
53+
}
54+
}
55+
56+
.ncFormBox__item {
57+
border-radius: var(--border-radius-small) !important;
58+
}
59+
60+
.ncFormBox_col {
61+
flex-direction: column;
62+
63+
.ncFormBox__item {
64+
&:first-child {
65+
border-start-start-radius: var(--border-radius-element) !important;
66+
border-start-end-radius: var(--border-radius-element) !important;
67+
}
68+
69+
&:last-child {
70+
border-end-start-radius: var(--border-radius-element) !important;
71+
border-end-end-radius: var(--border-radius-element) !important;
72+
}
73+
}
74+
}
75+
76+
.ncFormBox_row {
77+
flex-direction: row;
78+
79+
.ncFormBox__item {
80+
flex: 1 1;
81+
82+
&:first-child {
83+
border-start-start-radius: var(--border-radius-element) !important;
84+
border-end-start-radius: var(--border-radius-element) !important;
85+
}
86+
87+
&:last-child {
88+
border-end-end-radius: var(--border-radius-element) !important;
89+
border-start-end-radius: var(--border-radius-element) !important;
90+
}
91+
}
92+
}
93+
</style>
94+
95+
<docs>
96+
### General
97+
98+
Visually group form elements with a small gap and rounded corners forming a solid group for supported components.
99+
100+
**Note**: if the group has a semantic meaning, consider using the `<NcFormGroup>` component.
101+
102+
```vue
103+
<script>
104+
export default {
105+
data() {
106+
return {
107+
text: 'Text',
108+
option: 'One'
109+
}
110+
}
111+
}
112+
</script>
113+
114+
<template>
115+
<NcFormBox>
116+
<NcTextField v-model="text" label="Text Field" />
117+
<NcTextField v-model="text" label="Text Field" />
118+
<NcTextField v-model="text" label="Text Field" />
119+
<NcSelect v-model="option" input-label="Select Field" :options="['One', 'Two', 'Three']" />
120+
</NcFormBox>
121+
</template>
122+
```
123+
124+
### Advanced usage
125+
126+
Use scoped slots params to apply the item class to custom items.
127+
128+
```vue
129+
<template>
130+
<div>
131+
<h4>NcButton without a group</h4>
132+
<div>
133+
<NcButton wide>
134+
First button
135+
</NcButton>
136+
<NcButton wide>
137+
Second button
138+
</NcButton>
139+
<NcButton wide>
140+
Third button
141+
</NcButton>
142+
</div>
143+
144+
<h4>NcButton inside NcFormBox with scoped-slot</h4>
145+
<NcFormBox v-slot="{ itemClass }">
146+
<NcButton :class="itemClass" wide>
147+
First button
148+
</NcButton>
149+
<NcButton :class="itemClass" wide>
150+
Second button
151+
</NcButton>
152+
<NcButton :class="itemClass" wide>
153+
Third button
154+
</NcButton>
155+
</NcFormBox>
156+
</div>
157+
</template>
158+
```
159+
</docs>

src/components/NcFormBox/index.ts

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 './NcFormBox.vue'
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { InjectionKey } from 'vue'
7+
8+
import { inject } from 'vue'
9+
10+
export const NC_FORM_BOX_CONTEXT_KEY: InjectionKey<{
11+
isInFormBox: false
12+
formBoxItemClass: undefined
13+
} | {
14+
isInFormBox: true
15+
formBoxItemClass: string
16+
}> = Symbol.for('NcFormBox:context')
17+
18+
/**
19+
* Get NcFormBox context with a fallback
20+
* TODO: make it public?
21+
*/
22+
export function useNcFormBox() {
23+
return inject(NC_FORM_BOX_CONTEXT_KEY, {
24+
isInFormBox: false,
25+
formBoxItemClass: undefined,
26+
})
27+
}

0 commit comments

Comments
 (0)