Files
Lauri Ojansivu 6bb926af19 A checked checkbox sits on its own square in right-to-left languages too.
#6786's first fix drew the tick with physical borders, so it is a check mark
and not "<" right-to-left. But where it sits was still logical: the tick is a
box narrowed from 13px to 7px and rotated about its bottom-right corner, placed
with inset-inline-start: -3px and margin-inline-end: 6px. A right-to-left page
started the narrowed box at the square's RIGHT edge and pushed it further
right, so the tick sat beside its box - "out of box" in the issue's Admin Panel
screenshots. Measured in Chromium, the right-to-left tick spanned 303-323px
beside a square at 291-308px.

The shared tick, the Admin Panel's, the sidebar's and the Clean themes' are
placed with left and margin-right now: 291-311px, on the square, at the same
place as left-to-right, and 7 + 4 + 6 = 17px keeps the label where it was. The
import page, whose rows add a gap before the label, keeps that gap on the
reading side with a right-to-left rule. Toggle switches and the board list's
corner box, which are not this tick, still mirror.

tests/rtlCheckMark.test.cjs fails if any rule that draws a tick places it with
a logical offset or margin; tests/rtl.test.js allows physical margins and a
left offset only inside checked-state rules; the Playwright spec measures that
the tick is on its square in both directions and the label does not move.

Thanks to mimZD and xet7 !

Fixes #6786.
2026-10-24 20:12:50 +03:00

913 lines
21 KiB
CSS

select,
textarea,
input:not([type=file]),
button {
box-sizing: border-box;
background-color: #ebebeb;
border: 1px solid #ccc;
border-radius: 6px;
display: block;
margin-bottom: 14px;
min-height: 41px;
padding: 9px 14px;
}
select.full,
textarea.full,
input:not([type=file]).full,
button.full {
width: 100%;
}
select.input-error,
textarea.input-error,
input:not([type=file]).input-error,
button.input-error {
background-color: #ece9e9;
border-color: #ba1212;
}
select:focus,
textarea:focus,
input:not([type=file]):focus,
button:focus {
outline: 0;
}
input[type="file"] {
margin-bottom: 18px;
}
input[type="radio"] {
-webkit-appearance: radio;
appearance: radio;
min-height: inherit;
}
input[type="text"],
input[type="password"],
input[type="email"] {
transition: background 85ms ease-in, border-color 85ms ease-in;
width: min(250px, 30vw);
}
input[type="text"].inline-input,
input[type="password"].inline-input,
input[type="email"].inline-input {
background: none;
border: 0;
margin: 0;
padding: 3px;
min-height: 0;
height: 23px;
width: min(200px, 25vw);
}
input[type="text"].full-line,
input[type="password"].full-line,
input[type="email"].full-line {
width: 100%;
}
input[type="email"]:invalid {
box-shadow: none;
}
input[type="text"]:hover,
input[type="password"]:hover,
input[type="email"]:hover,
textarea:hover {
border-color: #999;
}
input[type="text"]:hover.input-error,
input[type="password"]:hover.input-error,
input[type="email"]:hover.input-error,
textarea:hover.input-error {
border-color: #ba1212;
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
textarea:focus {
background: #fff;
border-color: #318ec4;
box-shadow: 0 0 2px #318ec4;
}
input[type="text"]:focus.input-error,
input[type="password"]:focus.input-error,
input[type="email"]:focus.input-error,
textarea:focus.input-error {
background-color: #f8f7f7;
border-color: #ba1212;
box-shadow: 0 0 2px #d11515;
}
input[type="text"]:disabled,
input[type="password"]:disabled,
input[type="email"]:disabled,
textarea:disabled {
background-color: #dcdcdc;
border-color: #bfbfbf;
color: #222;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
select {
max-height: 40vh;
width: min(256px, 32vw);
margin-bottom: 9px;
}
select.inline {
width: 100%;
}
option[disabled] {
color: #222;
}
textarea {
height: 180px;
transition: background 85ms ease-in, border-color 85ms ease-in;
resize: vertical;
width: 100%;
}
textarea.editor {
resize: none;
padding-bottom: 27px;
}
.button {
border-radius: 3px;
text-decoration: none;
position: relative;
}
input[type="submit"],
button {
background: var(--theme-accent-fill, var(--theme-accent, #01628c));
border: none;
cursor: pointer;
display: inline-block;
font-weight: 700;
line-height: 1.3;
padding: 9px 35px;
text-align: center;
color: var(--theme-button-text, #fff);
}
input[type="submit"] .wide,
button .wide {
padding-inline-start: 56px;
padding-inline-end: 56px;
}
input[type="submit"]:hover,
button:hover,
input[type="submit"]:focus,
button:focus {
background: var(--theme-accent-fill, var(--theme-accent, #01628c));
color: var(--theme-button-text, #fff);
}
input[type="submit"]:active,
button:active {
background: var(--theme-accent-fill, var(--theme-accent, #01628c));
box-shadow: inset 0 3px 6px rgba(0,0,0,0.3);
color: var(--theme-button-text, #fff);
}
input[type="submit"]:active:hover,
button:active:hover,
input[type="submit"]:active:focus,
button:active:focus,
input[type="submit"]:active:active,
button:active:active {
background: var(--theme-accent-fill, var(--theme-accent, #01628c));
}
/* The submit button of a useraccounts form inside a popup is a `.primary` too.
Member Settings / Change Password renders `+atForm(state='changePwd')`, whose
button is `button.at-btn#at-btn` with no class of ours on it - so it fell back
to the plain grey `button` above, while the Save button of Edit Profile beside
it in the same menu was the themed one. Two buttons, one menu, two looks.
It is named in the SAME rules rather than given a copy of them, so the theme
accent and the hover and active states have one home.
docs/Features/General/Theme/Change-Color.md */
input[type="submit"].primary,
button.primary,
.pop-over .at-form button.at-btn {
background: var(--theme-accent, #005377);
box-shadow: 0 1px 0 #4d4d4d;
color: #fff;
}
input[type="submit"].primary:hover,
button.primary:hover,
.pop-over .at-form button.at-btn:hover,
input[type="submit"].primary:focus,
button.primary:focus,
.pop-over .at-form button.at-btn:focus {
background: var(--theme-accent, #004766);
}
input[type="submit"].primary:active,
button.primary:active,
.pop-over .at-form button.at-btn:active {
background: var(--theme-accent, #01628c);
}
input[type="submit"].negate,
button.negate {
background: #eb5a46;
box-shadow: 0 1px 0 #4d4d4d;
color: #fff;
}
input[type="submit"].negate:hover,
button.negate:hover,
input[type="submit"].negate:focus,
button.negate:focus {
background: #990f0f;
background: linear-gradient(#990f0f, #7d0c0c);
box-shadow: 0 1px 0 #4d4d4d;
color: #fff;
}
input[type="submit"].negate:active,
button.negate:active {
background: #7d0c0c;
box-shadow: 0 1px 0 #4d4d4d;
color: #fff;
}
input[type="submit"] i.fa,
button i.fa {
margin-inline-end: 10px;
}
input[type="submit"].disabled,
input[type="submit"]:disabled,
input[type="button"].disabled,
button.disabled,
.button.disabled,
input[type="submit"].disabled:hover,
input[type="submit"]:disabled:hover,
input[type="button"].disabled:hover,
button.disabled:hover,
.button.disabled:hover,
input[type="submit"].disabled:active,
input[type="submit"]:disabled:active,
input[type="button"].disabled:active,
button.disabled:active,
.button.disabled:active {
background: #555;
cursor: default;
box-shadow: none;
color: #999;
}
fieldset {
border: 1px solid #bfbfbf;
padding: 18px 28px;
margin-bottom: 18px;
}
input[type="hidden"] {
display: none;
}
.radio-div,
.check-div {
display: block;
margin: 0 0 5px 35px;
min-height: 23px;
position: relative;
}
.radio-div input,
.check-div input {
inset-inline-start: -35px;
min-height: 0;
margin: 0;
padding: 0;
position: absolute;
top: 3px;
}
.radio-div label,
.check-div label {
font-weight: 400;
}
label {
display: block;
font-weight: 700;
margin-bottom: 5px;
}
label.form-error {
color: #d32f2f;
}
input::-webkit-input-placeholder,
textarea::-webkit-input-placeholder,
input::-moz-placeholder,
textarea::-moz-placeholder {
color: #333 !important;
}
.edit-controls,
.add-controls {
display: flex;
align-items: center;
margin-top: 0px;
margin-bottom: 14px;
}
@media print {
.add-controls {
display: none;
}
}
.edit-controls button[type=submit],
.add-controls button[type=submit],
.edit-controls input[type=button],
.add-controls input[type=button] {
float: inline-start;
height: 41px;
margin-bottom: 0px;
}
/* "or Template or link a list from another board": each alternative is its own
span.quiet, and in this flex row a span is a flex item, which drops the space
its text begins with - so the second read "Templateor link...". The gap is
the span's own margin instead of a space the layout throws away. */
.edit-controls > .quiet + .quiet,
.add-controls > .quiet + .quiet {
margin-inline-start: 0.3em;
}
.edit-controls .fa-times-thin,
.add-controls .fa-times-thin {
font-size: calc(20px * var(--wekan-ui-font-scale, 1));
margin: 5px 21px;
}
/* Custom-field Copy is an editor action above the top-right corner of the
field controls, not part of the Save / Close row. */
.js-card-customfield-text,
.js-card-customfield-number,
.js-card-customfield-checkbox-editor,
.js-card-customfield-currency,
.js-card-customfield-dropdown,
.js-card-customfield-stringtemplate,
.custom-field-date-editor {
position: relative;
}
.custom-field-copy-control {
inset-inline-end: 0;
position: absolute;
top: -24px;
z-index: 1;
}
form.inlined-form .custom-field-copy-control > a.fa-copy,
.custom-field-date-editor .custom-field-copy-control > a.fa-copy {
float: none;
inset: auto;
position: static;
top: auto;
}
/* CJK (Japanese/Chinese/Korean) labels contain no spaces, so browsers may
insert a line break between any two characters. In the narrow add-card /
edit footers this made short labels such as "追加" (Add) and "リンク"
(Link) wrap mid-word. `word-break: keep-all` forbids breaks inside CJK
words while leaving Latin wrapping behavior unchanged, and `flex-wrap`
lets the footer wrap between its links instead of inside them.
See https://github.com/wekan/wekan/issues/4023 */
.edit-controls,
.add-controls {
flex-wrap: wrap;
word-break: keep-all;
}
.edit-controls button[type=submit],
.add-controls button[type=submit] {
white-space: nowrap;
}
.add-controls .quiet {
white-space: nowrap;
}
[type="checkbox"]:not(:checked),
[type="checkbox"]:checked {
position: absolute;
inset-inline-start: -9999px;
visibility: hidden;
display: none;
}
.materialCheckBox {
position: relative;
width: 13px;
height: 13px;
z-index: 0;
border: 2px solid #5a5a5a;
border-radius: 1px;
transition: 0.2s;
margin: 0;
cursor: pointer;
/* A CHECKBOX IS SQUARE, WHEREVER IT IS PUT.
Several of the rows that carry one are flex containers - the export popup's
"select what to include", Admin Panel's setting rows, the sidebar's filters
- and a flex item shrinks by default. Next to a long label that squeezed the
13px box into a thin vertical sliver: "Kortin tiedot (jokainen kortti kuten
kortin viennissä)" drew a sliver while "Taulu" beside it stayed square.
`flex: none` here rather than in each of those places, because the shape of
a checkbox is this rule's business and there are 92 of them across 19
templates. */
flex: none;
}
/* #6786: the tick is physical geometry - a box narrowed from 13px to 7px and
rotated 40deg about its bottom-right corner, drawn with its bottom and right
borders - so where it sits is physical too. With `inset-inline-start` and
`margin-inline-end` a right-to-left page started the narrowed box at the
RIGHT edge of the square and pushed it further right: the tick sat beside
its box, "out of box". `left: -3px` and `margin-right: 6px` put it on the
square's left edge in both directions, and 7 + 4 + 6 = 17px is still the
square's width, so the label beside it does not move. */
.materialCheckBox.is-checked {
top: -4px;
left: -3px;
width: 7px;
height: 15px;
margin-right: 6px;
border-top: 2px solid transparent;
border-left: 2px solid transparent;
border-bottom: 2px solid #3cb500;
border-right: 2px solid #3cb500;
transform: rotate(40deg);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transform-origin: 100% 100%;
}
.button-link {
background: #fff;
background: linear-gradient(#fff, #f5f5f5);
border-radius: 3px;
box-sizing: border-box;
-webkit-user-select: none;
user-select: none;
border: 1px solid #e3e3e3;
border-bottom-color: #c2c2c2;
cursor: pointer;
display: block;
font-weight: 700;
height: 34px;
margin-top: 6px;
max-width: 300px;
padding: 7px;
position: relative;
text-decoration: none;
overflow: ellipsis;
}
.button-link .on {
background: #48b512;
background: linear-gradient(#48b512, #3d990f);
border-radius: 3px;
color: #fff;
display: none;
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
font-weight: 700;
height: 17px;
line-height: calc(17px * var(--wekan-ui-font-scale, 1));
margin: 0;
padding: 2px 4px;
position: absolute;
inset-inline-end: 5px;
top: 5px;
text-align: center;
}
.button-link.is-on {
padding-inline-end: 30px;
max-width: 196px;
}
.button-link.is-on .on {
display: block;
}
.button-link.inline {
color: #666;
padding: 2px 14px;
margin-inline-start: 4px;
}
.button-link.setting {
height: 52px;
float: inline-start;
position: relative;
margin-top: 0;
}
.button-link.setting.disabled {
background: #fff;
border-color: #e9e9e9;
color: #222;
cursor: default;
}
.button-link.setting.disabled select {
display: none;
}
.button-link.setting.disabled:hover .label {
color: #222;
}
.button-link.setting.disabled,
.button-link.setting.disabled:hover,
.button-link.setting.disabled:active,
.button-link.setting.disabled.primary,
.button-link.setting.disabled.primary:hover,
.button-link.setting.disabled.primary:active {
background: #555;
border-color: #444;
border-bottom-color: #333;
cursor: default;
box-shadow: none;
color: #999;
}
.button-link.setting .label {
color: #222;
display: block;
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
line-height: calc(14px * var(--wekan-ui-font-scale, 1));
margin-bottom: 0;
}
.button-link.setting:hover .label {
color: #eee;
}
.button-link.setting .value {
display: block;
font-size: calc(18px * var(--wekan-ui-font-scale, 1));
line-height: calc(24px * var(--wekan-ui-font-scale, 1));
overflow: hidden;
text-overflow: ellipsis;
}
.button-link.setting label {
display: none;
}
.button-link.setting select {
border: none;
cursor: pointer;
height: 50px;
inset-inline-start: 0;
margin: 0;
opacity: 0;
position: absolute;
top: 0;
z-index: 2;
width: 100%;
}
.button-link:hover {
background: #318ec4;
background: linear-gradient(#318ec4, #2b7cab);
border-color: #2e85b8;
color: #fff;
}
.button-link:hover .on {
background-image: none;
background-color: rgba(255,255,255,0.3);
border-color: transparent;
}
.button-link:active {
background: #2e85b8;
background: linear-gradient(#2e85b8, #28739f);
border-color: #2b7cab;
color: #fff;
}
.button-link .button-link.negate:hover {
background: #990f0f;
background: linear-gradient(#990f0f, #7d0c0c);
border-color: linear-gradient(#990f0f, #7d0c0c);
}
.button-link .button-link.negate:active {
background: #7d0c0c;
border-color: #990f0f;
}
.button-link.primary {
background: #48b512;
background: linear-gradient(#48b512, #3d990f);
border: 1px solid;
border-color: #3d990f;
color: #fff;
}
.button-link.primary:hover {
background: #3d990f;
background: linear-gradient(#3d990f, #327d0c);
border-color: #3d990f;
}
.button-link.danger {
background: #ba1212;
background: linear-gradient(#ba1212, #8b0e0e);
border: 1px solid;
border-color: #a21010;
color: #fff;
}
.button-link.danger:hover {
background: #a21010;
background: linear-gradient(#a21010, #740b0b);
border-color: #8b0e0e;
}
button.quiet-button,
button.loud-text-button {
background: none;
text-align: start;
line-height: normal;
border: none;
box-shadow: none;
}
button.quiet-button:active,
button.loud-text-button:active {
color: #4d4d4d;
background: #d3d3d3;
box-shadow: none;
}
button.quiet-button {
font-weight: 400;
text-decoration: underline;
}
button.loud-text-button {
width: 100%;
}
button.loud-text-button:hover {
color: #111;
}
.emphasis-button,
.quiet-button {
border-radius: 3px;
-webkit-user-select: none;
user-select: none;
color: #222;
display: block;
margin: 2px 0;
padding: 6px 8px;
position: relative;
}
.emphasis-button.w-img,
.quiet-button.w-img {
padding-inline-start: 28px;
}
.emphasis-button:hover,
.quiet-button:hover {
color: #4d4d4d;
background: #dcdcdc;
}
.emphasis-button:active,
.quiet-button:active {
color: #4d4d4d;
background: #d3d3d3;
}
.quiet-button-large {
padding: 16px 24px;
}
.emphasis-button {
color: #74663e;
background: #ecdfbb;
}
.emphasis-button:hover {
color: #53492d;
background: #e7d6a7;
}
.emphasis-button:active {
color: #53492d;
background: #e1cc93;
}
.is-editable {
cursor: pointer;
}
.big-link {
border-radius: 3px;
display: block;
margin: 6px 0 6px 40px;
padding: 11px;
position: relative;
text-decoration: none;
font-size: calc(16px * var(--wekan-ui-font-scale, 1));
line-height: calc(20px * var(--wekan-ui-font-scale, 1));
}
.big-link .text {
text-decoration: underline;
}
.big-link:hover {
background: #dcdcdc;
}
.big-link.options {
padding-inline-end: 41px;
}
.big-link .option {
height: 30px;
width: 30px;
position: absolute;
inset-inline-end: 6px;
top: 6px;
}
.big-link.none {
color: #222;
text-decoration: none;
}
.big-link.none:hover {
background: transparent;
}
.big-link.avatar-changer {
padding-inline-end: 51px;
}
.big-link.avatar-changer .member {
border: 1px solid #ccc;
border-radius: 3px;
height: 40px;
width: 40px;
position: absolute;
inset-inline-end: 0;
top: 0;
}
.big-link.avatar-changer .member .member-avatar {
height: 40px;
width: 40px;
}
.big-link.avatar-changer .member .member-initials {
font-size: calc(16px * var(--wekan-ui-font-scale, 1));
height: 40px;
line-height: calc(40px * var(--wekan-ui-font-scale, 1));
max-height: 40px;
}
.show-more {
border-radius: 3px;
color: #222;
display: block;
padding: 16px 8px 16px 40px;
margin: 8px 0;
}
.show-more:hover {
background: #dcdcdc;
text-decoration: underline;
}
.show-more.compact {
padding: 12px 8px;
margin: 8px 0 0;
text-align: center;
}
.board-widget .show-more {
padding: 12px 8px 12px 40px;
}
.uploader {
clear: both;
cursor: pointer;
position: relative;
height: 34px;
width: 100%;
}
.uploader .realfile {
cursor: pointer;
height: 34px;
line-height: calc(34px * var(--wekan-ui-font-scale, 1));
position: absolute;
top: 0;
inset-inline-start: 0;
width: 100%;
z-index: 2;
font-size: calc(23px * var(--wekan-ui-font-scale, 1));
}
.uploader .realfile input[type="file"] {
cursor: pointer;
height: 34px;
line-height: calc(34px * var(--wekan-ui-font-scale, 1));
margin: 0;
opacity: 0;
padding: 0;
width: 100%;
z-index: 2;
font-size: calc(23px * var(--wekan-ui-font-scale, 1));
}
.uploader:hover .fakefile {
background: #318ec4;
background: linear-gradient(#318ec4, #2b7cab);
border-color: #2e85b8;
color: #fff;
}
.dropdown-menu {
border-radius: 2px;
overflow: hidden;
}
.dropdown-menu li {
border-top: none;
}
.dropdown-menu li a {
padding: 4px 12px 4px 8px;
}
.dropdown-menu li a img {
width: 18px;
height: 18px;
margin-inline-end: 5px;
vertical-align: middle;
}
.dropdown-menu li a .minicard-label {
width: 11px;
height: 11px;
border-radius: 2px;
margin: 2px 7px -2px -2px;
display: inline-block;
}
.dropdown-menu li.active {
background: #005377;
}
.dropdown-menu li.active a,
.dropdown-menu li.active .quiet {
color: #fff;
}
.material-toggle-switch {
display: flex;
}
.toggle-label {
position: relative;
display: block;
height: 20px;
width: 44px;
background-color: #a6a6a6;
border-radius: 100px;
cursor: pointer;
transition: all 0.3s ease;
}
.toggle-label:after {
position: absolute;
inset-inline-start: -2px;
top: -3px;
display: block;
width: 26px;
height: 26px;
border-radius: 100px;
background-color: #fff;
box-shadow: 0px 3px 3px rgba(0,0,0,0.05);
content: '';
transition: all 0.3s ease;
}
.toggle-label:active:after {
transform: scale(1.15, 0.85);
}
.toggle-switch:checked ~ .toggle-label {
background-color: #6fbeb5;
}
.toggle-switch:checked ~ .toggle-label:after {
inset-inline-start: 20px;
background-color: #179588;
}
.toggle-switch:checked:disabled ~ .toggle-label {
background-color: #d5d5d5;
pointer-events: none;
}
.toggle-switch:checked:disabled ~ .toggle-label:after {
background-color: #bcbdbc;
}
.material-toggle-switch input.toggle-switch[type="checkbox"] {
display: block;
visibility: visible;
position: absolute;
inset-inline-start: auto;
width: 1px;
height: 1px;
margin: 0;
padding: 0;
clip-path: inset(50%);
overflow: hidden;
}
.material-toggle-switch .toggle-switch:focus-visible ~ .toggle-label {
outline: 2px solid currentColor;
outline-offset: 4px;
}
.toggle-switch-title {
margin: 0 0.5em;
display: flex;
}
@media screen and (max-width: 800px) {
.edit-controls .fa-times-thin,
.add-controls .fa-times-thin {
margin: 3px 20px;
}
}
/* Creation choices stay visible below the title, including empty composers. */
.multiline-title-choice {
border: 0;
padding: 0;
margin: 8px 0;
min-width: 0;
}
.multiline-title-choice legend {
font-weight: bold;
margin-bottom: 4px;
}
.multiline-title-choice label {
display: flex;
align-items: center;
gap: 6px;
margin: 4px 0;
}
.multiline-title-choice input[type="radio"] {
width: auto;
margin: 0;
flex: 0 0 auto;
}
/* Text editors can be resized by their native corner handle. Autosize's inline
resize:none/horizontal must not hide the user's two-dimensional control. */
body textarea {
resize: both !important;
overflow: auto !important;
max-height: none !important;
max-width: 100%;
min-height: 2.5em;
}
textarea[rows] {
height: auto;
}
/* Clean Light's ordinary controls use a pale accent; its primary/destructive
buttons have their own darker fills and foregrounds in boardColors.css. */
.board-color-cleanlight {
--theme-button-text: #1a1a1a;
}
/* The due date a typed title will give its card (titleDueHint). */
.title-due-hint {
display: flex;
align-items: center;
gap: 6px;
margin: 4px 0;
padding: 2px 6px;
border-radius: 4px;
background: #e8f2f8;
color: #01628c;
font-size: 0.9em;
}
.title-due-hint-text {
flex: 1 1 auto;
min-width: 0;
}
.title-due-hint-dismiss {
flex: 0 0 auto;
color: inherit;
cursor: pointer;
padding: 0 4px;
}