Skip to content

Commit f9fef98

Browse files
authored
Merge pull request #4168 from nextcloud/design/sticky-div-with-drop-shadow
Move sticky with drop shadow to new component
2 parents cc1d9cb + 5aaf0c2 commit f9fef98

12 files changed

Lines changed: 246 additions & 130 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
# Changelog
66
All notable changes to this project will be documented in this file.
77

8+
## [unreleased]
9+
### changes
10+
- Make vote cell focusable
11+
- Make shadow of sticky items transparent
12+
813
## [8.1.4] - 2025-07-15
914
### Fixes
1015
- Fixed some typos

src/assets/scss/globals.scss

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,3 @@
77
left: 0;
88
z-index: 5;
99
}
10-
11-
.sticky-top {
12-
--shadow-height: 10px;
13-
position: sticky;
14-
top: 0;
15-
z-index: 4;
16-
padding-bottom: 0px;
17-
padding-bottom: var(--shadow-height);
18-
19-
&::after {
20-
content: '';
21-
position: absolute;
22-
bottom: 0;
23-
left: -1px;
24-
right: 0;
25-
height: 0px;
26-
background: linear-gradient(
27-
to bottom,
28-
rgba(var(--color-box-shadow-rgb), 0.3),
29-
rgba(var(--color-box-shadow-rgb), 0)
30-
);
31-
transition:
32-
all var(--animation-slow) linear,
33-
border 1ms;
34-
}
35-
36-
&.sticky-bottom-shadow {
37-
border-top: 0;
38-
padding-bottom: var(--shadow-height);
39-
margin-bottom: 0;
40-
&::after {
41-
height: var(--shadow-height);
42-
}
43-
}
44-
}
45-
46-
.sticky-top.sticky-left {
47-
z-index: 6;
48-
}

src/components/Base/modules/Collapsible.vue

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -180,24 +180,25 @@ onBeforeUnmount(() => {
180180
observer?.disconnect()
181181
containerRef.value?.removeEventListener('scroll', updateOverflowIndicators)
182182
})
183+
184+
const wrapperClass = computed(() => ({
185+
collapsible_wrapper: true,
186+
'has-top-shadow': hasTopOverflow.value,
187+
'has-bottom-shadow': hasBottomOverflow.value,
188+
}))
189+
190+
const containerClass = computed(() => ({
191+
collapsible_container: true,
192+
'no-transition': drag.isDragging,
193+
}))
183194
</script>
184195

185196
<template>
186197
<div class="collapsible">
187-
<div
188-
:class="[
189-
'collapsible_wrapper',
190-
{
191-
'has-top-shadow': hasTopOverflow,
192-
'has-bottom-shadow': hasBottomOverflow,
193-
},
194-
]">
198+
<div :class="wrapperClass">
195199
<div
196200
ref="containerRef"
197-
:class="[
198-
'collapsible_container',
199-
{ 'no-transition': drag.isDragging },
200-
]"
201+
:class="containerClass"
201202
:style="{ height: height + 'px' }">
202203
<div ref="slotWrapper" class="collapsible_content">
203204
<slot />

src/components/Base/modules/HeaderBar.vue

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ function toggleClamp() {
4747
padding-inline: 56px 8px;
4848
background-color: var(--color-main-background);
4949
transition: all var(--animation-slow) linear;
50-
&.sticky-top {
51-
z-index: 9;
52-
}
5350
5451
&::after {
5552
border-top: 1px solid var(--color-border);

src/components/Base/modules/InputDiv.vue

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,22 @@ function subtract() {
183183
onMounted(() => {
184184
assertBoundaries()
185185
})
186+
const componentClass = computed(() => [
187+
'input-div',
188+
{ numeric: useNumModifiers || inputmode === 'numeric' },
189+
])
190+
191+
const inputClass = computed(() => [
192+
{
193+
'has-modifier': useNumModifiers && useNumericVariant,
194+
'has-submit': submit,
195+
},
196+
computedSignalingClass.value,
197+
])
186198
</script>
187199

188200
<template>
189-
<div
190-
:class="[
191-
'input-div',
192-
{ numeric: useNumModifiers || inputmode === 'numeric' },
193-
]">
201+
<div :class="componentClass">
194202
<label v-if="label">
195203
{{ label }}
196204
</label>
@@ -214,13 +222,7 @@ onMounted(() => {
214222
:type="type"
215223
:inputmode="inputmode"
216224
:placeholder="placeholder"
217-
:class="[
218-
{
219-
'has-modifier': useNumModifiers && useNumericVariant,
220-
'has-submit': submit,
221-
},
222-
computedSignalingClass,
223-
]"
225+
:class="inputClass"
224226
@input="emit('input')"
225227
@change="emit('change')"
226228
@keyup.enter="emit('submit')" />
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<script setup lang="ts">
7+
import { computed } from 'vue'
8+
9+
interface Props {
10+
stickyTop?: boolean
11+
stickyLeft?: boolean
12+
activateBottomShadow?: boolean
13+
activateRightShadow?: boolean
14+
}
15+
16+
const {
17+
stickyTop = false,
18+
stickyLeft = false,
19+
activateBottomShadow = false,
20+
activateRightShadow = false,
21+
} = defineProps<Props>()
22+
23+
const stickyClass = computed(() => ({
24+
container: true,
25+
'sticky-top': stickyTop,
26+
'sticky-left': stickyLeft,
27+
'sticky-bottom-shadow': activateBottomShadow,
28+
'sticky-right-shadow': activateRightShadow,
29+
}))
30+
</script>
31+
32+
<template>
33+
<div :class="stickyClass">
34+
<slot name="default">
35+
<div class="inner"></div>
36+
</slot>
37+
</div>
38+
</template>
39+
40+
<style lang="scss" scoped>
41+
.container {
42+
--shadow-height: 10px;
43+
}
44+
45+
.inner {
46+
width: 100%;
47+
height: 100%;
48+
background-color: var(--color-main-background);
49+
}
50+
51+
.sticky-left {
52+
position: sticky;
53+
left: 0;
54+
z-index: 5;
55+
}
56+
57+
.sticky-top {
58+
--shadow-height: 10px;
59+
position: sticky;
60+
top: 0;
61+
z-index: 4;
62+
padding-bottom: 0px;
63+
padding-bottom: var(--shadow-height);
64+
65+
&::after {
66+
content: '';
67+
position: absolute;
68+
bottom: 0;
69+
left: -1px;
70+
right: 0;
71+
height: 0;
72+
background: linear-gradient(
73+
to bottom,
74+
rgba(var(--color-box-shadow-rgb), 0.3),
75+
rgba(var(--color-box-shadow-rgb), 0)
76+
);
77+
transition:
78+
all var(--animation-slow) linear,
79+
border 1ms;
80+
}
81+
82+
&.sticky-bottom-shadow {
83+
border-top: 0;
84+
padding-bottom: var(--shadow-height);
85+
margin-bottom: 0;
86+
&::after {
87+
height: var(--shadow-height);
88+
}
89+
}
90+
}
91+
92+
.sticky-top.sticky-left {
93+
z-index: 7;
94+
}
95+
96+
/* TODO: Implement sticky right shadow
97+
An Alternative could be using a grid instead of ::after
98+
to be able to position multiple shadows in all directions */
99+
100+
/*
101+
padding-right: var(--shadow-height);
102+
103+
&::after {
104+
content: '';
105+
position: absolute;
106+
right: 0;
107+
top: -1px;
108+
bottom: 0;
109+
width: 0;
110+
background: linear-gradient(
111+
to right,
112+
rgba(var(--color-box-shadow-rgb), 0.3),
113+
rgba(var(--color-box-shadow-rgb), 0)
114+
);
115+
transition:
116+
all var(--animation-slow) linear,
117+
border 1ms;
118+
}
119+
120+
&.sticky-right-shadow {
121+
border-right: 0;
122+
padding-right: var(--shadow-height);
123+
margin-right: 0;
124+
&::after {
125+
width: var(--shadow-height);
126+
}
127+
} */
128+
</style>

src/components/Comments/CommentItem.vue

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,8 @@ async function restoreComment(comment: Comment) {
8888
<div
8989
v-for="subComment in comment.comments"
9090
:key="subComment.id"
91-
:class="[
92-
'comment-item__sub-comment',
93-
{ deleted: subComment.deleted },
94-
]">
91+
class="comment-item__sub-comment"
92+
:class="{ deleted: subComment.deleted }">
9593
<!-- eslint-disable vue/no-v-html -->
9694
<span v-html="linkify(subComment.comment)" />
9795
<!-- eslint-enable vue/no-v-html -->

src/components/Options/OptionItem.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@ interface Props {
2020
2121
const { option, draggable = false, showOwner = false } = defineProps<Props>()
2222
23+
const containerClass = {
24+
'option-item-container': true,
25+
deleted: option.deleted !== 0,
26+
draggable,
27+
}
28+
2329
const pollStore = usePollStore()
2430
</script>
2531

2632
<template>
27-
<div :class="['option-item', { draggable, deleted: option.deleted !== 0 }]">
33+
<div :class="containerClass">
2834
<DragIcon v-if="draggable" class="grid-area-drag-icon" />
2935

3036
<OptionItemOwner
@@ -52,7 +58,7 @@ const pollStore = usePollStore()
5258
</template>
5359

5460
<style lang="scss">
55-
.option-item {
61+
.option-item-container {
5662
display: grid;
5763
grid-template-columns: auto 1fr auto auto;
5864
grid-template-areas: 'drag option owner actions';

src/components/User/UserItem.vue

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -210,18 +210,18 @@ function showMenu() {
210210
// TODO: implement
211211
return true
212212
}
213+
const componentClass = computed(() => [
214+
'user-item',
215+
typeComputed,
216+
{
217+
disabled,
218+
condensed,
219+
},
220+
])
213221
</script>
214222

215223
<template>
216-
<div
217-
:class="[
218-
'user-item',
219-
typeComputed,
220-
{
221-
disabled,
222-
condensed: condensed,
223-
},
224-
]">
224+
<div :class="componentClass">
225225
<div class="avatar-wrapper">
226226
<NcAvatar
227227
v-bind="avatarProps"

src/components/VoteTable/VoteParticipant.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { NcActionButton, NcActions, NcActionText } from '@nextcloud/vue'
1717
import DeleteIcon from 'vue-material-design-icons/Delete.vue'
1818
import VoteMenu from './VoteMenu.vue'
1919
import { User } from '../../Types/index.ts'
20+
import { computed } from 'vue'
2021
2122
const pollStore = usePollStore()
2223
const sessionStore = useSessionStore()
@@ -32,19 +33,18 @@ async function removeUser(userId: string) {
3233
await votesStore.resetUserVotes({ userId })
3334
showSuccess(t('polls', 'Participant {userId} has been removed', { userId }))
3435
}
36+
37+
const userItemClass = computed(() => ({
38+
'current-user': user.id === sessionStore.currentUser.id,
39+
}))
3540
</script>
3641

3742
<template>
3843
<UserItem
3944
v-if="pollStore.viewMode === 'table-view'"
4045
:user="user"
4146
condensed
42-
:class="[
43-
'participant',
44-
{
45-
'current-user': user.id === sessionStore.currentUser.id,
46-
},
47-
]">
47+
:class="userItemClass">
4848
<template
4949
v-if="
5050
pollStore.permissions.edit || user.id === sessionStore.currentUser.id

0 commit comments

Comments
 (0)