Skip to content

Commit d09e436

Browse files
committed
Extract add card form to keep tab order intact
Signed-off-by: Theo <36564257+theoholl@users.noreply.github.com>
1 parent c61e8e7 commit d09e436

2 files changed

Lines changed: 227 additions & 199 deletions

File tree

src/components/board/Stack.vue

Lines changed: 22 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212
'stack--dragging-card': draggingCard,
1313
}"
1414
:data-cy-stack="stack.title">
15-
<div v-click-outside="stopCardCreation"
16-
class="stack__header"
17-
:class="{'stack__header--add': showAddCard, 'stack__header--done-column': isDoneColumn}"
15+
<div class="stack__header"
16+
:class="{'stack__header--done-column': isDoneColumn}"
1817
:aria-label="stack.title">
1918
<transition name="fade" mode="out-in">
2019
<h3 v-if="!canManage || isArchived" tabindex="0">
@@ -100,6 +99,12 @@
10099
</div>
101100
</NcModal>
102101

102+
<StackCardAdd v-if="canAddCard && stackAddCardAtTop"
103+
:stack="stack"
104+
:add-at-top="true"
105+
@creating="animate = true"
106+
@created="handleCardCreated" />
107+
103108
<Container :get-child-payload="payloadForCard(stack.id)"
104109
class="dnd-container stack__cards-list"
105110
group-name="stack"
@@ -120,39 +125,10 @@
120125
</Draggable>
121126
</Container>
122127

123-
<div v-if="canAddCard" class="stack__card-add">
124-
<NcButton v-if="!showAddCard"
125-
data-cy="action:add-card"
126-
class="stack__card-add-button"
127-
type="tertiary"
128-
:wide="true"
129-
@click.stop="showAddCard=true">
130-
<template #icon>
131-
<PlusIcon :size="20" />
132-
</template>
133-
{{ t('deck', 'Add card') }}
134-
</NcButton>
135-
<form v-else
136-
:class="{ 'icon-loading-small': stateCardCreating }"
137-
@submit.prevent.stop="clickAddCard()">
138-
<label for="new-stack-input-main" class="hidden-visually">{{ t('deck', 'Add a new card') }}</label>
139-
<input id="new-stack-input-main"
140-
ref="newCardInput"
141-
v-model="newCardTitle"
142-
type="text"
143-
class="no-close"
144-
:disabled="stateCardCreating"
145-
:placeholder="t('deck', 'Card name')"
146-
required
147-
pattern=".*\S+.*"
148-
@focus="onCreateCardFocus"
149-
@keydown.esc.stop="showAddCard = false">
150-
<input v-show="!stateCardCreating"
151-
class="icon-confirm"
152-
type="submit"
153-
value="">
154-
</form>
155-
</div>
128+
<StackCardAdd v-if="canAddCard && !stackAddCardAtTop"
129+
:stack="stack"
130+
@creating="animate = true"
131+
@created="handleCardCreated" />
156132
</div>
157133
</template>
158134

@@ -162,11 +138,11 @@ import { mapGetters, mapState } from 'vuex'
162138
import { Container, Draggable } from 'vue-smooth-dnd'
163139
import ArchiveIcon from 'vue-material-design-icons/ArchiveOutline.vue'
164140
import CheckCircleOutline from 'vue-material-design-icons/CheckCircleOutline.vue'
165-
import PlusIcon from 'vue-material-design-icons/Plus.vue'
166-
import { NcActions, NcActionButton, NcButton, NcModal } from '@nextcloud/vue'
167-
import { showError, showUndo } from '@nextcloud/dialogs'
141+
import { NcActions, NcActionButton, NcModal } from '@nextcloud/vue'
142+
import { showUndo } from '@nextcloud/dialogs'
168143
169144
import CardItem from '../cards/CardItem.vue'
145+
import StackCardAdd from './StackCardAdd.vue'
170146
171147
import '@nextcloud/dialogs/style.css'
172148
import { mapActions } from 'pinia'
@@ -178,14 +154,13 @@ export default {
178154
components: {
179155
NcActions,
180156
NcActionButton,
181-
NcButton,
182157
CardItem,
158+
StackCardAdd,
183159
Container,
184160
Draggable,
185161
NcModal,
186162
ArchiveIcon,
187163
CheckCircleOutline,
188-
PlusIcon,
189164
},
190165
directives: {
191166
ClickOutside,
@@ -205,9 +180,6 @@ export default {
205180
editing: false,
206181
draggingCard: false,
207182
copiedStack: '',
208-
newCardTitle: '',
209-
showAddCard: false,
210-
stateCardCreating: false,
211183
animate: false,
212184
modalArchivAllCardsShow: false,
213185
stackTransfer: {
@@ -239,50 +211,20 @@ export default {
239211
dragHandleSelector() {
240212
return this.canEdit && !this.showArchived ? null : '.no-drag'
241213
},
242-
cardDetailsInModal: {
243-
get() {
244-
return this.$store.getters.config('cardDetailsInModal')
245-
},
246-
set(newValue) {
247-
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
248-
},
249-
},
250214
stackAddCardAtTop() {
251215
return this.$store.getters.config('stackAddCardAtTop') === true
252216
},
253217
canAddCard() {
254218
return this.canEdit && !this.showArchived && !this.isArchived
255219
},
256220
},
257-
watch: {
258-
showAddCard(newValue) {
259-
if (!newValue) {
260-
this.$store.dispatch('toggleShortcutLock', false)
261-
} else {
262-
this.$nextTick(() => {
263-
this.$refs.newCardInput.focus()
264-
})
265-
}
266-
},
267-
},
268-
269221
mounted() {
270222
this.setupAutoscrollOnDrag()
271223
},
272224
273225
methods: {
274226
...mapActions(useTrashbinStore, ['stackUndoDelete']),
275227
...mapActions(useStackStore, ['setDoneStack', 'deleteStack', 'updateStack']),
276-
stopCardCreation(e) {
277-
// For some reason the submit event triggers a MouseEvent that is bubbling to the outside
278-
// so we have to ignore it
279-
e.stopPropagation()
280-
if (this.$refs.newCardInput && this.$refs.newCardInput.parentElement === e.target.parentElement) {
281-
return false
282-
}
283-
this.showAddCard = false
284-
return false
285-
},
286228
async onDropCard(stackId, event) {
287229
const { addedIndex, removedIndex, payload } = event
288230
const card = Object.assign({}, payload)
@@ -343,41 +285,12 @@ export default {
343285
cancelEdit() {
344286
this.editing = false
345287
},
346-
async clickAddCard() {
347-
this.stateCardCreating = true
348-
try {
349-
const addCardAtTop = this.stackAddCardAtTop
350-
this.animate = true
351-
const newCard = await this.$store.dispatch('addCard', {
352-
title: this.newCardTitle,
353-
stackId: this.stack.id,
354-
boardId: this.stack.boardId,
355-
// Without an order the API appends the card to the end of the stack
356-
...(addCardAtTop ? { order: 0 } : {}),
357-
})
358-
if (addCardAtTop) {
359-
// Creating a card does not move the existing cards down, so reorder
360-
await this.$store.dispatch('reorderCard', { ...newCard, order: 0 })
361-
}
362-
this.newCardTitle = ''
363-
this.showAddCard = true
364-
this.$nextTick(() => {
365-
this.$refs.newCardInput.focus()
366-
this.animate = false
367-
// Refs of a v-for are registered in creation order, not in list order
368-
this.$refs.card?.find((card) => card.id === newCard.id)?.scrollIntoView()
369-
})
370-
if (!this.cardDetailsInModal) {
371-
this.$router.push({ name: 'card', params: { cardId: newCard.id } })
372-
}
373-
} catch (e) {
374-
showError('Could not create card: ' + e.response.data.message)
375-
} finally {
376-
this.stateCardCreating = false
377-
}
378-
},
379-
onCreateCardFocus() {
380-
this.$store.dispatch('toggleShortcutLock', true)
288+
handleCardCreated(newCard) {
289+
this.$nextTick(() => {
290+
this.animate = false
291+
// Refs of a v-for are registered in creation order, not in list order
292+
this.$refs.card?.find((card) => card.id === newCard.id)?.scrollIntoView()
293+
})
381294
},
382295
setupAutoscrollOnDrag() {
383296
let timer
@@ -436,18 +349,6 @@ export default {
436349
}
437350
438351
&.stack--add-card-at-top {
439-
.stack__header {
440-
order: 1;
441-
}
442-
443-
.stack__card-add {
444-
order: 2;
445-
}
446-
447-
.stack__cards-list {
448-
order: 3;
449-
}
450-
451352
&:after {
452353
content: '';
453354
display: block;
@@ -573,84 +474,6 @@ export default {
573474
}
574475
}
575476
576-
.stack__card-add {
577-
flex-shrink: 0;
578-
z-index: 100;
579-
display: flex;
580-
background-color: var(--color-main-background);
581-
position: relative;
582-
583-
.stack--add-card-at-top & {
584-
padding-top: $stack-gap;
585-
586-
&:after {
587-
content: '';
588-
display: block;
589-
position: absolute;
590-
width: 100%;
591-
height: $stack-gap;
592-
bottom: 0;
593-
z-index: 99;
594-
pointer-events: none;
595-
background-image: linear-gradient(180deg, var(--color-main-background) 0%, transparent 100%);
596-
transform: translateY(100%);
597-
}
598-
}
599-
600-
.stack--add-card-at-bottom & {
601-
padding-bottom: $stack-gap;
602-
}
603-
604-
// Smooth fade out of the cards at the top
605-
&:before {
606-
content: '';
607-
display: block;
608-
position: absolute;
609-
width: 100%;
610-
height: $stack-gap;
611-
z-index: 99;
612-
transition: bottom var(--animation-slow);
613-
background-image: linear-gradient(0deg, var(--color-main-background) 0%, transparent 100%);
614-
transform: translateY(-100%);
615-
}
616-
617-
:deep(.stack__card-add-button.button-vue) {
618-
--button-size: var(--stack-card-add-control-height);
619-
color: var(--color-text-maxcontrast);
620-
621-
&:hover:not(:disabled),
622-
&:focus-visible {
623-
color: var(--color-main-text);
624-
}
625-
}
626-
627-
form {
628-
display: flex;
629-
width: 100%;
630-
height: var(--stack-card-add-control-height);
631-
box-sizing: border-box;
632-
border: 2px solid var(--color-border-maxcontrast);
633-
border-radius: var(--border-radius-large);
634-
overflow: hidden;
635-
padding: 2px;
636-
}
637-
638-
&.icon-loading-small:after,
639-
&.icon-loading-small-dark:after {
640-
margin-inline-start: calc(50% - 25px);
641-
}
642-
643-
input[type=text] {
644-
flex-grow: 1;
645-
padding-inline-end: 16px;
646-
}
647-
648-
input {
649-
border: none;
650-
margin: 0;
651-
}
652-
}
653-
654477
.modal__content {
655478
width: 25vw;
656479
min-width: 250px;

0 commit comments

Comments
 (0)