Skip to content

Commit 0f2d958

Browse files
authored
Merge pull request #7938 from nextcloud-libraries/backport/7501/stable8
[stable8] fix: adjust input border styles for dark theme and NcSelect
2 parents bb0e655 + b6b9e98 commit 0f2d958

File tree

5 files changed

+203
-180
lines changed

5 files changed

+203
-180
lines changed

src/assets/input-border.scss

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
/**
7+
* Similar as inputBorder but without active styles.
8+
*/
9+
@mixin inputLikeBorder($legacySelector, $borderColor: var(--color-border-maxcontrast)) {
10+
--input-border-box-shadow-light: 0 -1px #{$borderColor},
11+
0 0 0 1px color-mix(in srgb, #{$borderColor}, 65% transparent);
12+
--input-border-box-shadow-dark: 0 1px #{$borderColor},
13+
0 0 0 1px color-mix(in srgb, #{$borderColor}, 65% transparent);
14+
--input-border-box-shadow: var(--input-border-box-shadow-light);
15+
border: none;
16+
border-radius: var(--border-radius-element);
17+
box-shadow: var(--input-border-box-shadow);
18+
19+
&:hover:not([disabled]) {
20+
box-shadow: 0 0 0 1px $borderColor;
21+
}
22+
23+
// override with system theme
24+
@media (prefers-color-scheme: dark) {
25+
--input-border-box-shadow: var(--input-border-box-shadow-dark);
26+
}
27+
28+
// override with nextcloud theme
29+
@at-root [data-theme-dark] #{&} {
30+
--input-border-box-shadow: var(--input-border-box-shadow-dark);
31+
}
32+
@at-root [data-theme-light] #{&} {
33+
--input-border-box-shadow: var(--input-border-box-shadow-light);
34+
}
35+
36+
@at-root #{$legacySelector} #{&} {
37+
box-shadow: 0 0 0 1px $borderColor;
38+
39+
&:hover:not([disabled]) {
40+
box-shadow: 0 0 0 2px $borderColor;
41+
}
42+
}
43+
}
44+
45+
/**
46+
* Create a consistent border for an input element.
47+
* With Nextcloud 32+ there is no real border anymore but we use a box-shadow.
48+
*/
49+
@mixin inputBorder($legacySelector, $borderColor: var(--color-border-maxcontrast)) {
50+
@include inputLikeBorder($legacySelector, $borderColor);
51+
52+
&:focus-within:not([disabled]),
53+
&:active:not([disabled]) {
54+
box-shadow: 0 0 0 2px $borderColor,
55+
0 0 0 4px var(--color-main-background) !important;
56+
}
57+
}

src/components/NcInputField/NcInputField.vue

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@ For a list of all available props and attributes, please check the [HTMLInputEle
106106
<script>
107107
import AlertCircle from 'vue-material-design-icons/AlertCircleOutline.vue'
108108
import Check from 'vue-material-design-icons/Check.vue'
109+
import NcButton from '../NcButton/NcButton.vue'
109110
import { useModelMigration } from '../../composables/useModelMigration.ts'
110111
import GenRandomId from '../../utils/GenRandomId.js'
111112
import { isLegacy32 } from '../../utils/legacy.ts'
112113
import { logger } from '../../utils/logger.ts'
113-
import NcButton from '../NcButton/index.js'
114114
115115
export default {
116116
name: 'NcInputField',
@@ -286,6 +286,7 @@ export default {
286286
287287
setup() {
288288
const model = useModelMigration('value', 'update:value', true)
289+
289290
return {
290291
isLegacy32,
291292
model,
@@ -369,12 +370,11 @@ export default {
369370
</script>
370371

371372
<style lang="scss" scoped>
373+
@use '../../assets/input-border.scss' as border;
372374
373375
.input-field {
374376
--input-border-color: var(--color-border-maxcontrast);
375377
--input-border-radius: var(--border-radius-element);
376-
// Used e.g. if border width differs between focused and unfocused we need to compensate to prevent jumping
377-
--input-border-width-offset: calc(var(--border-width-input-focused, 2px) - var(--border-width-input, 2px));
378378
// The padding before the input can start (leading button or border)
379379
--input-padding-start: var(--border-radius-large);
380380
// The padding where the input has to end (trailing button or border)
@@ -408,23 +408,15 @@ export default {
408408
409409
&__main-wrapper {
410410
height: var(--default-clickable-area);
411-
padding: var(--border-width-input, 2px);
411+
padding: var(--border-width-input-focused, 2px);
412412
position: relative;
413-
414-
&:not(:has([disabled])):has(input:focus),
415-
&:not(:has([disabled])):has(input:active) {
416-
padding: 0;
417-
}
418413
}
419414
420415
&__input {
416+
@include border.inputBorder('.input-field--legacy', var(--input-border-color));
421417
background-color: var(--color-main-background);
422418
color: var(--color-main-text);
423-
border: none;
424419
border-radius: var(--input-border-radius);
425-
box-shadow:
426-
0 -1px var(--input-border-color),
427-
0 0 0 1px color-mix(in srgb, var(--input-border-color), 65% transparent);
428420
429421
cursor: pointer;
430422
-webkit-appearance: textfield !important;
@@ -434,11 +426,11 @@ export default {
434426
font-size: var(--default-font-size);
435427
text-overflow: ellipsis;
436428
429+
padding-block: 0;
430+
padding-inline: var(--input-padding-start) var(--input-padding-end);
437431
height: 100% !important;
438432
min-height: unset;
439433
width: 100%;
440-
padding-block: var(--input-border-width-offset);
441-
padding-inline: calc(var(--input-padding-start) + var(--input-border-width-offset)) calc(var(--input-padding-end) + var(--input-border-width-offset));
442434
443435
&::placeholder {
444436
color: var(--color-text-maxcontrast);
@@ -458,17 +450,9 @@ export default {
458450
display: none;
459451
}
460452
461-
&:hover:not([disabled]) {
462-
box-shadow: 0 0 0 1px var(--input-border-color);;
463-
}
464-
465453
&:active:not([disabled]),
466454
&:focus:not([disabled]) {
467455
--input-border-color: var(--color-main-text);
468-
// Reset padding offset when focused
469-
--input-border-width-offset: 0px;
470-
border: var(--border-width-input-focused, 2px) solid var(--input-border-color);
471-
box-shadow: 0 0 0 2px var(--color-main-background) !important;
472456
}
473457
474458
&:focus + .input-field__label,
@@ -611,23 +595,5 @@ export default {
611595
}
612596
}
613597
}
614-
615-
&--legacy {
616-
.input-field__input {
617-
box-shadow: 0 0 0 1px var(--input-border-color) inset;
618-
}
619-
620-
.input-field__main-wrapper:hover:not(:has([disabled])) {
621-
padding: 0;
622-
623-
.input-field__input {
624-
--input-border-color: var(--color-main-text);
625-
// Reset padding offset when focused
626-
--input-border-width-offset: 0px;
627-
border: var(--border-width-input-focused, 2px) solid var(--input-border-color);
628-
box-shadow: 0 0 0 2px var(--color-main-background) !important;
629-
}
630-
}
631-
}
632598
}
633599
</style>

src/components/NcSelect/NcSelect.vue

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ export default {
325325
<VueSelect
326326
class="select"
327327
:class="{
328+
'select--legacy': isLegacy,
328329
'select--no-wrap': noWrap,
329330
'user-select': userSelect,
330331
}"
@@ -414,12 +415,13 @@ import { VueSelect } from '@nextcloud/vue-select'
414415
import Vue from 'vue'
415416
import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
416417
import Close from 'vue-material-design-icons/Close.vue'
418+
import NcEllipsisedOption from '../NcEllipsisedOption/NcEllipsisedOption.vue'
419+
import NcListItemIcon from '../NcListItemIcon/NcListItemIcon.vue'
420+
import NcLoadingIcon from '../NcLoadingIcon/NcLoadingIcon.vue'
417421
import { useModelMigration } from '../../composables/useModelMigration.ts'
418422
import { t } from '../../l10n.js'
419423
import GenRandomId from '../../utils/GenRandomId.js'
420-
import NcEllipsisedOption from '../NcEllipsisedOption/index.js'
421-
import NcListItemIcon from '../NcListItemIcon/index.js'
422-
import NcLoadingIcon from '../NcLoadingIcon/index.js'
424+
import { isLegacy32 as isLegacy } from '../../utils/legacy.ts'
423425
424426
import '@nextcloud/vue-select/dist/vue-select.css'
425427
@@ -860,6 +862,7 @@ export default {
860862
return {
861863
avatarSize,
862864
model,
865+
isLegacy,
863866
}
864867
},
865868
@@ -1013,6 +1016,8 @@ export default {
10131016
</script>
10141017
10151018
<style lang="scss">
1019+
@use '../../assets/input-border.scss' as border;
1020+
10161021
body {
10171022
/**
10181023
* Set custom vue-select CSS variables.
@@ -1082,7 +1087,7 @@ body {
10821087
10831088
.v-select.select {
10841089
/* Override default vue-select styles */
1085-
min-height: var(--default-clickable-area);
1090+
min-height: calc(var(--default-clickable-area) - 2 * var(--border-width-input));
10861091
min-width: 260px;
10871092
margin: 0 0 var(--default-grid-baseline);
10881093
@@ -1127,7 +1132,7 @@ body {
11271132
.vs__dropdown-toggle {
11281133
position: relative;
11291134
max-height: 100px;
1130-
padding: 0;
1135+
padding: var(--border-width-input);
11311136
overflow-y: auto;
11321137
}
11331138
@@ -1141,15 +1146,22 @@ body {
11411146
}
11421147
11431148
&.vs--open .vs__dropdown-toggle {
1144-
border-width: var(--border-width-input-focused);
1145-
outline: 2px solid var(--color-main-background);
11461149
border-color: var(--color-main-text);
11471150
border-bottom-color: transparent;
1151+
border-bottom-left-radius: 0;
1152+
border-bottom-right-radius: 0;
1153+
border-style: solid;
1154+
border-width: var(--border-width-input-focused);
1155+
outline: 2px solid var(--color-main-background);
1156+
padding: 0;
11481157
}
11491158
1150-
&:not(.vs--disabled, .vs--open) .vs__dropdown-toggle:hover {
1151-
outline: 2px solid var(--color-main-background);
1152-
border-color: var(--color-main-text);
1159+
&:not(.vs--disabled, .vs--open) {
1160+
.vs__dropdown-toggle:active,
1161+
.vs__dropdown-toggle:focus-within {
1162+
outline: 2px solid var(--color-main-background);
1163+
border-color: var(--color-main-text);
1164+
}
11531165
}
11541166
11551167
&.vs--disabled {
@@ -1216,6 +1228,10 @@ body {
12161228
}
12171229
}
12181230
1231+
.vs__dropdown-toggle {
1232+
@include border.inputLikeBorder('.select--legacy', var(--vs-border-color));
1233+
}
1234+
12191235
.vs__dropdown-menu {
12201236
border-width: var(--border-width-input-focused) !important;
12211237
border-color: var(--color-main-text) !important;
@@ -1228,7 +1244,7 @@ body {
12281244
padding: 4px !important;
12291245
12301246
&--floating {
1231-
/* Fallback styles overidden by programmatically set inline styles */
1247+
/* Fallback styles overridden by programmatically set inline styles */
12321248
width: max-content;
12331249
position: absolute;
12341250
top: 0;

src/components/NcTextArea/NcTextArea.vue

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ export default {
281281
282282
setup() {
283283
const model = useModelMigration('value', 'update:value', true)
284+
284285
return {
285286
isLegacy32,
286287
model,
@@ -357,6 +358,8 @@ export default {
357358
</script>
358359

359360
<style lang="scss" scoped>
361+
@use '../../assets/input-border.scss' as border;
362+
360363
.textarea {
361364
--input-border-color: var(--color-border-maxcontrast);
362365
--input-border-width-offset: calc(var(--border-width-input-focused, 2px) - var(--border-width-input, 2px));
@@ -373,42 +376,27 @@ export default {
373376
374377
&__main-wrapper {
375378
height: calc(var(--default-clickable-area) * 2);
376-
padding: var(--border-width-input, 2px);
379+
padding: var(--border-width-input-focused, 2px);
377380
position: relative;
378-
379-
&:not(:has([disabled])):has(textarea:focus),
380-
&:not(:has([disabled])):has(textarea:active) {
381-
padding: 0;
382-
}
383381
}
384382
385383
&__input {
386384
margin: 0;
387-
padding-block: calc(10px + var(--input-border-width-offset));
388-
padding-inline: calc(12px - var(--border-width-input, 2px) + var(--input-border-width-offset)); // align with label 8px margin label + 4px padding label - 2px border input
385+
padding-block: var(--border-radius-element);
386+
padding-inline: 10px; // align with label 8px margin label + 4px padding label - 2px border input
389387
width: 100%;
390388
font-size: var(--default-font-size);
391389
text-overflow: ellipsis;
392390
cursor: pointer;
393391
394392
background-color: var(--color-main-background);
395393
color: var(--color-main-text);
396-
// we use box shadow to create a border as this allows use to have a nice gradient
397-
border: none;
398-
border-radius: var(--border-radius-element, var(--border-radius-large));
399-
box-shadow:
400-
0 -1px var(--input-border-color),
401-
0 0 0 1px color-mix(in srgb, var(--input-border-color), 65% transparent);
402-
403-
&:hover:not([disabled]) {
404-
box-shadow: 0 0 0 1px var(--input-border-color);
405-
}
394+
@include border.inputBorder('.textarea--legacy', var(--input-border-color));
395+
406396
&:active:not([disabled]),
407397
&:focus:not([disabled]) {
408398
--input-border-width-offset: 0px;
409399
--input-border-color: var(--color-main-text);
410-
border: var(--border-width-input-focused, 2px) solid var(--input-border-color);
411-
box-shadow: 0 0 0 2px var(--color-main-background) !important;
412400
}
413401
414402
// Hide placeholder while not focussed -> show label instead (only if internal label is used)
@@ -492,24 +480,5 @@ export default {
492480
color: var(--color-success-text);
493481
}
494482
}
495-
496-
// for Nextcloud 31 and older we need the old design with only one color
497-
&--legacy {
498-
.textarea__input {
499-
box-shadow: 0 0 0 1px var(--input-border-color);
500-
}
501-
502-
.textarea__main-wrapper:hover:not(:has([disabled])) {
503-
padding: 0;
504-
505-
.textarea__input {
506-
--input-border-color: var(--color-main-text);
507-
// Reset padding offset when focused
508-
--input-border-width-offset: 0px;
509-
border: var(--border-width-input-focused, 2px) solid var(--input-border-color);
510-
box-shadow: 0 0 0 2px var(--color-main-background) !important;
511-
}
512-
}
513-
}
514483
}
515484
</style>

0 commit comments

Comments
 (0)