Fix the broken input focus outline, unify input styling, tokenize scales
The root cause of 'some inputs show a focus outline, most don't': the
global input:focus/textarea:focus/select:focus rule (outline:none,
border-color: accent) has specificity (0,0,1,1). Every per-component
rule shaped '.wrapper input { border: 1px solid var(--border) }' (.form
input, .field-row input, .kanban__new input, .sidebar__select select,
.modal__field select) ties it exactly, and -- being unconditional --
silently won that tie by simply appearing later in the file, so the
input's border stayed var(--border) forever regardless of focus. Only
two controls escaped: .project-card__name-input (a class applied
directly to the element, (0,0,1,0), too low to ever win the tie) and
.card-row__text (has its own .card-row__text:focus, (0,0,2,0),
genuinely higher). Confirmed with a live computed-style test against
the real stylesheet before and after.
Fix: one canonical .field class (plus .field--compact for inline 'add'
rows and the sidebar, .field--autosize for the dashboard's JS-grown
textarea), applied directly to the <input>/<textarea>/<select> itself
in every view -- structurally immune to the same tie, since a bare
class can never out-specificity input:focus. This also collapses five
near-duplicate rules (each with its own padding/radius/font-size) into
one definition, and fixes .kanban__new input's stray
background: var(--surface) (every other field uses var(--bg); this one
nearly matched its own var(--surface) sidebar panel).
Also: .btn-danger hardcoded #b3261e/#fff instead of the already-
existing var(--error)/var(--accent-text) tokens, so danger buttons
didn't adapt in dark mode like every other error-coloured element;
now they do. Added .field:disabled styling (opacity/cursor), matching
buttons -- latent until now since no input bound :disabled yet.
Second pass, promoting repeated-but-consistent raw values to tokens:
- border-radius: 6/7/8/10/12/999px -- the stray 7px (.sidebar__link)
folded into the 6px tier -- become --radius-sm/md/lg/xl/pill.
- disabled/ghost opacity: 0.6 was used for every disabled button
except .status-row__delete's 0.4 (now unified) and drag-ghost states'
0.5 (semantically different, kept separate) -- --opacity-disabled/
--opacity-ghost.
Font-size and spacing values also repeat but don't reduce to a clean
scale without arbitrary judgement calls either way -- left alone.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -74,6 +74,44 @@ dashboard; the grid and the sidebar dropdown both pick it up via the shared
|
|||||||
`projects` store). Signed-out routes
|
`projects` store). Signed-out routes
|
||||||
(`/login`, `/verify-email`) render without the sidebar.
|
(`/login`, `/verify-email`) render without the sidebar.
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
`style.css` is one global stylesheet (no scoped/component styles) with a
|
||||||
|
handful of conventions worth knowing before adding to it:
|
||||||
|
|
||||||
|
- **Tokens** (`:root` custom properties): colours (`--bg`, `--surface`,
|
||||||
|
`--border`, `--text`, `--muted`, `--accent`(-text), `--error`, `--warn-bg`/
|
||||||
|
`-border`), a border-radius scale (`--radius-sm` 6px compact controls,
|
||||||
|
`--radius-md` 8px buttons/inputs, `--radius-lg` 10px tiles, `--radius-xl`
|
||||||
|
12px panels, `--radius-pill`), and two opacity values (`--opacity-disabled`
|
||||||
|
0.6, `--opacity-ghost` 0.5 for a dragged item's placeholder). Reach for one
|
||||||
|
of these before hand-writing a value that's really just "the same grey
|
||||||
|
border again" or "the same rounding as every other button."
|
||||||
|
- **`.field`** is the one themed `<input>`/`<textarea>`/`<select>` look, used
|
||||||
|
everywhere from the login form to the sidebar's project switcher.
|
||||||
|
`.field--compact` is the same thing smaller, for a control that's a flex
|
||||||
|
child beside a button (an inline "add" row) or squeezed into the sidebar.
|
||||||
|
`.field--autosize` adds the `resize:none; overflow:hidden` the dashboard's
|
||||||
|
JS-driven auto-growing textarea needs. Crucially, **`.field` is applied
|
||||||
|
directly to the control**, not to a wrapper (`.form input` no longer
|
||||||
|
exists) -- see the next point for why that's load-bearing, not just style.
|
||||||
|
- **Put component classes on the control itself, not a wrapping element**,
|
||||||
|
for anything that needs a `:focus` state. `input:focus`/`textarea:focus`/
|
||||||
|
`select:focus` (global, removes the default outline and colours the border
|
||||||
|
with `--accent` instead) has specificity `(0,0,1,1)` -- one pseudo-class,
|
||||||
|
one element. A rule shaped `.wrapper input { border-color: var(--border) }`
|
||||||
|
ties it exactly, and being unconditional, silently wins that tie by simply
|
||||||
|
appearing later in the file -- the input keeps `var(--border)` forever,
|
||||||
|
focused or not, no matter what `:focus` says. `.field`, applied straight to
|
||||||
|
the element, is `(0,0,1,0)` -- strictly lower, so it can never win that
|
||||||
|
tie regardless of source order. (This bit a real shipped version of the
|
||||||
|
app: half the inputs had a working focus ring and half silently didn't,
|
||||||
|
purely from which pattern each one happened to use.) A component that
|
||||||
|
needs its own more elaborate `:focus` state (`.card-row__text`, background
|
||||||
|
swap included) writes `.card-row__text:focus` explicitly -- specificity
|
||||||
|
`(0,0,2,0)`, genuinely higher, wins outright rather than by luck of
|
||||||
|
ordering.
|
||||||
|
|
||||||
## Inbox
|
## Inbox
|
||||||
|
|
||||||
A card with no project lives in the caller's inbox (`useInboxStore`), rendered
|
A card with no project lives in the caller's inbox (`useInboxStore`), rendered
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ async function onInboxChange(change: ColumnChange) {
|
|||||||
<rect x="6.25" y="2" width="3.5" height="9" rx="1" />
|
<rect x="6.25" y="2" width="3.5" height="9" rx="1" />
|
||||||
<rect x="11.5" y="2" width="3.5" height="6" rx="1" />
|
<rect x="11.5" y="2" width="3.5" height="6" rx="1" />
|
||||||
</svg>
|
</svg>
|
||||||
<select v-model="selectedProjectId" aria-label="Go to project">
|
<select v-model="selectedProjectId" class="field field--compact" aria-label="Go to project">
|
||||||
<option value="" disabled>Choose a project…</option>
|
<option value="" disabled>Choose a project…</option>
|
||||||
<option v-for="project in projects.projects" :key="project.id" :value="project.id">
|
<option v-for="project in projects.projects" :key="project.id" :value="project.id">
|
||||||
{{ project.title }}
|
{{ project.title }}
|
||||||
@@ -167,6 +167,7 @@ async function onInboxChange(change: ColumnChange) {
|
|||||||
<form class="kanban__new" @submit.prevent="onCreateInboxCard">
|
<form class="kanban__new" @submit.prevent="onCreateInboxCard">
|
||||||
<input
|
<input
|
||||||
v-model="newInboxText"
|
v-model="newInboxText"
|
||||||
|
class="field field--compact"
|
||||||
type="text"
|
type="text"
|
||||||
maxlength="1000"
|
maxlength="1000"
|
||||||
required
|
required
|
||||||
|
|||||||
+97
-106
@@ -14,6 +14,19 @@
|
|||||||
/* Kanban inbox column: a touch lighter than a status column (var(--bg)). */
|
/* Kanban inbox column: a touch lighter than a status column (var(--bg)). */
|
||||||
--inbox-bg: #fcfcfd;
|
--inbox-bg: #fcfcfd;
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
|
|
||||||
|
/* Same radius used consistently by everything of that kind -- not a full
|
||||||
|
scale, just names for the handful of values already in use. */
|
||||||
|
--radius-sm: 6px; /* compact controls: chips, small icon buttons, .field--compact */
|
||||||
|
--radius-md: 8px; /* buttons, .field, dropdown menus */
|
||||||
|
--radius-lg: 10px; /* tiles: project cards, kanban columns */
|
||||||
|
--radius-xl: 12px; /* panels: .card, .sidebar, .modal__dialog */
|
||||||
|
--radius-pill: 999px; /* status chip */
|
||||||
|
|
||||||
|
/* Both used consistently for their one meaning each -- not "disabled at
|
||||||
|
0.6, ghost at 0.5" by convention, just what those states are. */
|
||||||
|
--opacity-disabled: 0.6;
|
||||||
|
--opacity-ghost: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
@@ -36,7 +49,14 @@
|
|||||||
|
|
||||||
/* No browser focus ring on form controls -- the border colour change is
|
/* No browser focus ring on form controls -- the border colour change is
|
||||||
highlight enough (components with more elaborate focus styles, e.g.
|
highlight enough (components with more elaborate focus styles, e.g.
|
||||||
.card-row__text, override this with their own higher-specificity rule). */
|
.card-row__text, override this with their own higher-specificity rule).
|
||||||
|
Specificity is a single pseudo-class + element, (0,0,1,1) -- .field below
|
||||||
|
is deliberately a single class with no element in the selector, (0,0,1,0),
|
||||||
|
so it can never win a specificity tie against this regardless of source
|
||||||
|
order (a wrapper-class + descendant-element rule, e.g. the old ".form
|
||||||
|
input", ties with this rule instead and -- being unconditional -- can
|
||||||
|
silently beat it by simply appearing later in the file, permanently
|
||||||
|
hiding the focus state; that was a real, previously-shipped bug). */
|
||||||
input:focus,
|
input:focus,
|
||||||
textarea:focus,
|
textarea:focus,
|
||||||
select:focus {
|
select:focus {
|
||||||
@@ -44,6 +64,47 @@ select:focus {
|
|||||||
border-color: var(--accent);
|
border-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The one themed text input/select/textarea, applied directly to the
|
||||||
|
control itself everywhere -- login, dashboard, project, configuration,
|
||||||
|
profile, sidebar. See the note above for why "directly to the control"
|
||||||
|
(a single class, no wrapping selector) matters here, not just for
|
||||||
|
consistency. */
|
||||||
|
.field {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0.55rem 0.7rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font: inherit;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field:disabled {
|
||||||
|
opacity: var(--opacity-disabled);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Same control, smaller -- inline "add" rows and the sidebar, where it's a
|
||||||
|
flex child beside a button or icon rather than filling a form row. */
|
||||||
|
.field--compact {
|
||||||
|
flex: 1;
|
||||||
|
width: auto;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0.4rem 0.5rem;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The dashboard's project-name field: grown by JS to fit its content, so no
|
||||||
|
manual resize handle or scrollbar. */
|
||||||
|
.field--autosize {
|
||||||
|
resize: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||||
@@ -81,7 +142,7 @@ body {
|
|||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
padding: 0.35rem 0.55rem;
|
padding: 0.35rem 0.55rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -140,7 +201,7 @@ body {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 1rem 0.75rem 1.5rem;
|
padding: 1rem 0.75rem 1.5rem;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: var(--radius-xl);
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +218,7 @@ body {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: 0.3rem 0.5rem;
|
padding: 0.3rem 0.5rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__close:hover {
|
.sidebar__close:hover {
|
||||||
@@ -219,7 +280,7 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.6rem;
|
gap: 0.6rem;
|
||||||
padding: 0.45rem 0.6rem;
|
padding: 0.45rem 0.6rem;
|
||||||
border-radius: 7px;
|
border-radius: var(--radius-sm);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
@@ -272,18 +333,6 @@ body {
|
|||||||
padding: 0.2rem 0.6rem;
|
padding: 0.2rem 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__select select {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
font: inherit;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
padding: 0.35rem 0.4rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 6px;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar__inbox-cards {
|
.sidebar__inbox-cards {
|
||||||
max-height: 40vh;
|
max-height: 40vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -298,7 +347,7 @@ body {
|
|||||||
.card {
|
.card {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: var(--radius-xl);
|
||||||
padding: 1.75rem;
|
padding: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +374,7 @@ h1 {
|
|||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
background: var(--warn-bg);
|
background: var(--warn-bg);
|
||||||
border: 1px solid var(--warn-border);
|
border: 1px solid var(--warn-border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +421,7 @@ h1 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.passkey-notice__dismiss:hover {
|
.passkey-notice__dismiss:hover {
|
||||||
@@ -397,7 +446,7 @@ h1 {
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
padding: 0.6rem 0.75rem;
|
padding: 0.6rem 0.75rem;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,7 +473,7 @@ h1 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
padding: 0.3rem 0.5rem;
|
padding: 0.3rem 0.5rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.passkeys__remove:hover {
|
.passkeys__remove:hover {
|
||||||
@@ -450,7 +499,7 @@ h1 {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 10px;
|
border-radius: var(--radius-lg);
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@@ -481,22 +530,6 @@ h1 {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card__name-input {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 0.55rem 0.7rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
font: inherit;
|
|
||||||
font-size: 1rem;
|
|
||||||
/* Grown in JS to fit its content -- no manual resize handle, no scrollbar. */
|
|
||||||
resize: none;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- project detail: cards ------------------------------------------ */
|
/* --- project detail: cards ------------------------------------------ */
|
||||||
|
|
||||||
.cards {
|
.cards {
|
||||||
@@ -512,7 +545,7 @@ h1 {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
padding: 0.4rem 0.55rem;
|
padding: 0.4rem 0.55rem;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
}
|
}
|
||||||
@@ -520,7 +553,7 @@ h1 {
|
|||||||
.card-row__status {
|
.card-row__status {
|
||||||
flex: none;
|
flex: none;
|
||||||
padding: 0.15rem 0.55rem;
|
padding: 0.15rem 0.55rem;
|
||||||
border-radius: 999px;
|
border-radius: var(--radius-pill);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -538,7 +571,7 @@ h1 {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -563,7 +596,7 @@ h1 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-row__delete:hover {
|
.card-row__delete:hover {
|
||||||
@@ -625,7 +658,7 @@ h1 {
|
|||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
padding: 0.35rem 0.7rem;
|
padding: 0.35rem 0.7rem;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
@@ -653,7 +686,7 @@ h1 {
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -667,7 +700,7 @@ h1 {
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
font: inherit;
|
font: inherit;
|
||||||
padding: 0.45rem 0.6rem;
|
padding: 0.45rem 0.6rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,14 +766,14 @@ h1 {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
padding: 0.5rem 0.6rem;
|
padding: 0.5rem 0.6rem;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-row--ghost {
|
.status-row--ghost {
|
||||||
opacity: 0.5;
|
opacity: var(--opacity-ghost);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-row__name {
|
.status-row__name {
|
||||||
@@ -757,7 +790,7 @@ h1 {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-row__delete:hover:not(:disabled) {
|
.status-row__delete:hover:not(:disabled) {
|
||||||
@@ -765,7 +798,7 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.status-row__delete:disabled {
|
.status-row__delete:disabled {
|
||||||
opacity: 0.4;
|
opacity: var(--opacity-disabled);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,23 +811,11 @@ h1 {
|
|||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-row input {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
font: inherit;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
padding: 0.4rem 0.5rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 6px;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-row button[type='submit'] {
|
.field-row button[type='submit'] {
|
||||||
flex: none;
|
flex: none;
|
||||||
padding: 0.4rem 0.7rem;
|
padding: 0.4rem 0.7rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal__field {
|
.modal__field {
|
||||||
@@ -804,15 +825,6 @@ h1 {
|
|||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal__field select {
|
|
||||||
padding: 0.5rem 0.6rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
font: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- kanban board ---------------------------------------------------- */
|
/* --- kanban board ---------------------------------------------------- */
|
||||||
|
|
||||||
.kanban {
|
.kanban {
|
||||||
@@ -827,7 +839,7 @@ h1 {
|
|||||||
flex: 0 0 16rem;
|
flex: 0 0 16rem;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 10px;
|
border-radius: var(--radius-lg);
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,7 +874,7 @@ h1 {
|
|||||||
ghost placeholder while something is dragged over it. */
|
ghost placeholder while something is dragged over it. */
|
||||||
.kanban__cards:empty {
|
.kanban__cards:empty {
|
||||||
border: 1px dashed var(--border);
|
border: 1px dashed var(--border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanban__cards:empty::before {
|
.kanban__cards:empty::before {
|
||||||
@@ -879,14 +891,14 @@ h1 {
|
|||||||
.kanban-card {
|
.kanban-card {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
padding: 0.55rem 0.65rem;
|
padding: 0.55rem 0.65rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanban-card--ghost {
|
.kanban-card--ghost {
|
||||||
opacity: 0.5;
|
opacity: var(--opacity-ghost);
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanban__new {
|
.kanban__new {
|
||||||
@@ -895,23 +907,11 @@ h1 {
|
|||||||
margin-top: 0.6rem;
|
margin-top: 0.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanban__new input {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
font: inherit;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
padding: 0.4rem 0.5rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 6px;
|
|
||||||
background: var(--surface);
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.kanban__new button[type="submit"] {
|
.kanban__new button[type="submit"] {
|
||||||
flex: none;
|
flex: none;
|
||||||
padding: 0.4rem 0.7rem;
|
padding: 0.4rem 0.7rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- confirmation modal --------------------------------------------------- */
|
/* --- confirmation modal --------------------------------------------------- */
|
||||||
@@ -939,7 +939,7 @@ h1 {
|
|||||||
max-width: 24rem;
|
max-width: 24rem;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: var(--radius-xl);
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -958,7 +958,7 @@ h1 {
|
|||||||
.btn-secondary,
|
.btn-secondary,
|
||||||
.btn-danger {
|
.btn-danger {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
padding: 0.5rem 0.9rem;
|
padding: 0.5rem 0.9rem;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
@@ -971,14 +971,14 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-danger {
|
.btn-danger {
|
||||||
background: #b3261e;
|
background: var(--error);
|
||||||
border-color: #b3261e;
|
border-color: var(--error);
|
||||||
color: #fff;
|
color: var(--accent-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:disabled,
|
.btn-secondary:disabled,
|
||||||
.btn-danger:disabled {
|
.btn-danger:disabled {
|
||||||
opacity: 0.6;
|
opacity: var(--opacity-disabled);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1000,20 +1000,11 @@ h1 {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form input {
|
|
||||||
padding: 0.55rem 0.7rem;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[type="submit"],
|
button[type="submit"],
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
padding: 0.6rem 1rem;
|
padding: 0.6rem 1rem;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: var(--radius-md);
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: var(--accent-text);
|
color: var(--accent-text);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -1028,7 +1019,7 @@ button[type="submit"],
|
|||||||
|
|
||||||
button[type="submit"]:disabled,
|
button[type="submit"]:disabled,
|
||||||
.btn-primary:disabled {
|
.btn-primary:disabled {
|
||||||
opacity: 0.6;
|
opacity: var(--opacity-disabled);
|
||||||
cursor: progress;
|
cursor: progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ async function onCreate() {
|
|||||||
<textarea
|
<textarea
|
||||||
ref="titleInput"
|
ref="titleInput"
|
||||||
v-model="title"
|
v-model="title"
|
||||||
class="project-card__name-input"
|
class="field field--autosize"
|
||||||
rows="1"
|
rows="1"
|
||||||
maxlength="255"
|
maxlength="255"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ async function onPasskeyLogin() {
|
|||||||
<form v-else class="form" @submit.prevent="onSubmit">
|
<form v-else class="form" @submit.prevent="onSubmit">
|
||||||
<label>
|
<label>
|
||||||
<span>Email</span>
|
<span>Email</span>
|
||||||
<input v-model="email" type="email" autocomplete="email" required />
|
<input v-model="email" class="field" type="email" autocomplete="email" required />
|
||||||
<small v-if="error?.fieldError('email')" class="field-error">
|
<small v-if="error?.fieldError('email')" class="field-error">
|
||||||
{{ error.fieldError('email') }}
|
{{ error.fieldError('email') }}
|
||||||
</small>
|
</small>
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ async function onRemovePasskey(passkey: Passkey) {
|
|||||||
<form class="form" @submit.prevent="onChangeEmail">
|
<form class="form" @submit.prevent="onChangeEmail">
|
||||||
<label>
|
<label>
|
||||||
<span>New email</span>
|
<span>New email</span>
|
||||||
<input v-model="newEmail" type="email" maxlength="255" required />
|
<input v-model="newEmail" class="field" type="email" maxlength="255" required />
|
||||||
<small v-if="changeError?.fieldError('email')" class="field-error">
|
<small v-if="changeError?.fieldError('email')" class="field-error">
|
||||||
{{ changeError.fieldError('email') }}
|
{{ changeError.fieldError('email') }}
|
||||||
</small>
|
</small>
|
||||||
@@ -197,7 +197,7 @@ async function onRemovePasskey(passkey: Passkey) {
|
|||||||
<form class="form form--new-card" @submit.prevent="onAddPasskey">
|
<form class="form form--new-card" @submit.prevent="onAddPasskey">
|
||||||
<label>
|
<label>
|
||||||
<span>Label</span>
|
<span>Label</span>
|
||||||
<input v-model="newLabel" type="text" maxlength="100" />
|
<input v-model="newLabel" class="field" type="text" maxlength="100" />
|
||||||
</label>
|
</label>
|
||||||
<p v-if="addError" class="form-error">{{ addError }}</p>
|
<p v-if="addError" class="form-error">{{ addError }}</p>
|
||||||
<button type="submit" :disabled="adding">
|
<button type="submit" :disabled="adding">
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
|
|||||||
<form class="field-row" @submit.prevent="onRenameProject">
|
<form class="field-row" @submit.prevent="onRenameProject">
|
||||||
<input
|
<input
|
||||||
v-model="nameDraft"
|
v-model="nameDraft"
|
||||||
|
class="field field--compact"
|
||||||
type="text"
|
type="text"
|
||||||
maxlength="255"
|
maxlength="255"
|
||||||
required
|
required
|
||||||
@@ -251,6 +252,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
|
|||||||
<form class="field-row" @submit.prevent="onAddStatus">
|
<form class="field-row" @submit.prevent="onAddStatus">
|
||||||
<input
|
<input
|
||||||
v-model="newStatusName"
|
v-model="newStatusName"
|
||||||
|
class="field field--compact"
|
||||||
type="text"
|
type="text"
|
||||||
maxlength="100"
|
maxlength="100"
|
||||||
required
|
required
|
||||||
@@ -282,7 +284,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
|
|||||||
|
|
||||||
<label class="modal__field">
|
<label class="modal__field">
|
||||||
<span>Move cards to</span>
|
<span>Move cards to</span>
|
||||||
<select v-model="reassignTo">
|
<select v-model="reassignTo" class="field">
|
||||||
<option value="" disabled>Choose a status…</option>
|
<option value="" disabled>Choose a status…</option>
|
||||||
<option v-for="s in reassignOptions" :key="s.id" :value="s.id">{{ s.name }}</option>
|
<option v-for="s in reassignOptions" :key="s.id" :value="s.id">{{ s.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ async function onCreate() {
|
|||||||
<form class="form form--new-card" @submit.prevent="onCreate">
|
<form class="form form--new-card" @submit.prevent="onCreate">
|
||||||
<label>
|
<label>
|
||||||
<span>New card</span>
|
<span>New card</span>
|
||||||
<input v-model="newText" type="text" maxlength="1000" required />
|
<input v-model="newText" class="field" type="text" maxlength="1000" required />
|
||||||
</label>
|
</label>
|
||||||
<button type="submit" :disabled="submitting">
|
<button type="submit" :disabled="submitting">
|
||||||
{{ submitting ? 'Adding…' : 'Add card' }}
|
{{ submitting ? 'Adding…' : 'Add card' }}
|
||||||
|
|||||||
Reference in New Issue
Block a user