refactor(ui): auth views migrate onto the shared buttons; two variants added
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 42s
CI & Build / integration (push) Successful in 1m39s
CI & Build / Python tests (push) Successful in 2m4s
CI & Build / Build & push image (push) Canceled after 0s
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 42s
CI & Build / integration (push) Successful in 1m39s
CI & Build / Python tests (push) Successful in 2m4s
CI & Build / Build & push image (push) Canceled after 0s
The five auth views each defined .btn-submit identically — full-width, filled, 0.6rem — and LoginView additionally defined .btn-oauth. Those rules are now gone entirely rather than tokenised: the template composes `btn-primary btn-block` and `btn-ghost btn-block`, and there is nothing left per-file to drift. That is the difference between this and the earlier chunks. Consolidating the core four moved geometry into one place but left every semantic name defining its own; this removes the definition. Two variants added, both earned rather than invented: - .btn-text — no fill, no border. The most common shape in the dense surfaces (dismiss, cancel-beside-confirm, clear-search) where a border would draw a box around something that should read as an action on the adjacent text. Distinct from ghost, which IS a box. - .btn-danger-outline — already existed independently in three views before this sheet, which is what makes it a variant and not a one-off. It is what a delete looks like when it must not shout. .btn-block composes with a variant rather than being one, because width is orthogonal to appearance. Also corrected the sheet's own header, which claimed "no template changes" — true when it was written, false as of this commit. It now states the actual model: a button is variant + size, composed in the template. Semantic per-view names are named as the thing that drifted, and why: a name says what a button is FOR and nothing about what it should look like, so two buttons doing the same job in two views had no reason to match, and didn't. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
This commit is contained in:
@@ -9,9 +9,19 @@
|
||||
* because a scoped duplicate is invisible to every tool — it isn't a rule
|
||||
* violation, isn't a broken reference, and isn't a recorded snippet.
|
||||
*
|
||||
* GEOMETRY LIVES HERE; the class names are the app's existing ones so no
|
||||
* template changes. Every value is a design-system token, so a palette or scale
|
||||
* change moves the buttons rather than stranding a copy that no longer matches.
|
||||
* GEOMETRY LIVES HERE. Every value is a design-system token, so a palette or
|
||||
* scale change moves the buttons rather than stranding a copy that no longer
|
||||
* matches.
|
||||
*
|
||||
* A button is `variant + size`, composed in the template:
|
||||
* btn-primary a page action
|
||||
* btn-primary btn-compact a row action
|
||||
* btn-ghost btn-inline an affordance inside a card
|
||||
* btn-primary btn-block a form's single submitting action
|
||||
* Semantic per-view names (.btn-save, .btn-delete-task, …) were the thing that
|
||||
* drifted, because a name says what a button is FOR and nothing about what it
|
||||
* should look like — so two buttons doing the same job in two views had no
|
||||
* reason to match, and didn't.
|
||||
*
|
||||
* MIGRATION NOTE — this file is deliberately safe to land ahead of the removals.
|
||||
* These are plain selectors (specificity 0,1,0); a Vue `<style scoped>` rule
|
||||
@@ -25,7 +35,8 @@
|
||||
.btn-primary,
|
||||
.btn-secondary,
|
||||
.btn-ghost,
|
||||
.btn-danger {
|
||||
.btn-danger,
|
||||
.btn-danger-outline {
|
||||
padding: var(--fs-space-2) var(--fs-space-4); /* 8px 16px */
|
||||
border: none;
|
||||
border-radius: var(--fs-radius-md); /* 8px — the system's button radius */
|
||||
@@ -46,7 +57,8 @@
|
||||
.btn-primary:disabled,
|
||||
.btn-secondary:disabled,
|
||||
.btn-ghost:disabled,
|
||||
.btn-danger:disabled {
|
||||
.btn-danger:disabled,
|
||||
.btn-danger-outline:disabled {
|
||||
opacity: var(--fs-disabled-opacity);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@@ -54,7 +66,8 @@
|
||||
.btn-primary:focus-visible,
|
||||
.btn-secondary:focus-visible,
|
||||
.btn-ghost:focus-visible,
|
||||
.btn-danger:focus-visible {
|
||||
.btn-danger:focus-visible,
|
||||
.btn-danger-outline:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--fs-focus-ring);
|
||||
}
|
||||
@@ -105,6 +118,38 @@
|
||||
background: var(--color-action-destructive-hover);
|
||||
}
|
||||
|
||||
/* A bare text button: no fill, no border. The most common shape in the dense
|
||||
* surfaces — a dismiss, a cancel next to a confirm, a clear-search — where a
|
||||
* border would draw a box around something that should read as an action on
|
||||
* the text beside it. Distinct from ghost, which IS a box. */
|
||||
.btn-text {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-muted);
|
||||
padding: var(--fs-space-1) var(--fs-space-2);
|
||||
font-family: var(--fs-font-body);
|
||||
font-size: var(--fs-size-tiny);
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: color var(--fs-dur-fast) var(--fs-ease);
|
||||
}
|
||||
.btn-text:not(:disabled):hover { color: var(--color-text); }
|
||||
.btn-text:disabled { opacity: var(--fs-disabled-opacity); cursor: not-allowed; }
|
||||
.btn-text:focus-visible { outline: none; box-shadow: var(--fs-focus-ring); }
|
||||
|
||||
/* Destructive, outlined — fills on hover. Already existed independently in
|
||||
* three views before this sheet, which is what makes it a variant rather than
|
||||
* a one-off: it is what a delete looks like when it must not shout. */
|
||||
.btn-danger-outline {
|
||||
background: none;
|
||||
border: 1px solid var(--color-action-destructive);
|
||||
color: var(--color-action-destructive);
|
||||
}
|
||||
.btn-danger-outline:not(:disabled):hover {
|
||||
background: var(--color-action-destructive);
|
||||
color: var(--fs-text-on-action);
|
||||
}
|
||||
|
||||
/* --- size modifiers ------------------------------------------------------
|
||||
*
|
||||
* THREE sizes, because the app genuinely has three. Measured across the ~100
|
||||
@@ -135,3 +180,14 @@
|
||||
font-size: var(--fs-size-tiny);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Full width, for a form's single submitting action — the auth screens. Width
|
||||
* is orthogonal to size, so it composes: `btn-primary btn-block`. */
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: var(--fs-space-3) var(--fs-space-4); /* 12px 16px — a touch taller,
|
||||
because a full-width button
|
||||
is the page's main action */
|
||||
font-size: var(--fs-size-body-sm);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ async function handleSubmit() {
|
||||
/>
|
||||
</div>
|
||||
<p v-if="error" class="error-msg">{{ error }}</p>
|
||||
<button type="submit" class="btn-submit" :disabled="submitting">
|
||||
<button type="submit" class="btn-primary btn-block" :disabled="submitting">
|
||||
{{ submitting ? "Sending..." : "Send Reset Link" }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -137,24 +137,6 @@ async function handleSubmit() {
|
||||
.success-msg p {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 0.6rem;
|
||||
background: var(--color-action-primary);
|
||||
color: var(--fs-text-on-action);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
font-weight: var(--fs-weight-medium);
|
||||
}
|
||||
.btn-submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
.btn-submit:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.auth-footer {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
|
||||
@@ -84,7 +84,7 @@ function loginWithOAuth() {
|
||||
<p class="forgot-link">
|
||||
<router-link to="/forgot-password">Forgot your password?</router-link>
|
||||
</p>
|
||||
<button type="submit" class="btn-submit" :disabled="submitting">
|
||||
<button type="submit" class="btn-primary btn-block" :disabled="submitting">
|
||||
{{ submitting ? "Signing in..." : "Sign In" }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -98,7 +98,7 @@ function loginWithOAuth() {
|
||||
|
||||
<button
|
||||
v-if="authStore.oauthEnabled"
|
||||
class="btn-oauth"
|
||||
class="btn-ghost btn-block"
|
||||
@click="loginWithOAuth"
|
||||
>
|
||||
Login with Authentik
|
||||
@@ -176,24 +176,6 @@ function loginWithOAuth() {
|
||||
font-size: 0.9rem;
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 0.6rem;
|
||||
background: var(--color-action-primary);
|
||||
color: var(--fs-text-on-action);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
font-weight: var(--fs-weight-medium);
|
||||
}
|
||||
.btn-submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
.btn-submit:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -208,20 +190,6 @@ function loginWithOAuth() {
|
||||
flex: 1;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
.btn-oauth {
|
||||
width: 100%;
|
||||
padding: 0.6rem;
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
font-weight: var(--fs-weight-medium);
|
||||
}
|
||||
.btn-oauth:hover {
|
||||
background: var(--color-bg-hover, var(--color-border));
|
||||
}
|
||||
.auth-footer {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
|
||||
@@ -144,7 +144,7 @@ async function handleSubmit() {
|
||||
<p v-if="passwordMismatch" class="error-hint">Passwords do not match</p>
|
||||
</div>
|
||||
<p v-if="error" class="error-msg">{{ error }}</p>
|
||||
<button type="submit" class="btn-submit" :disabled="!canSubmit">
|
||||
<button type="submit" class="btn-primary btn-block" :disabled="!canSubmit">
|
||||
{{ submitting ? "Creating Account..." : "Create Account" }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -247,24 +247,6 @@ async function handleSubmit() {
|
||||
font-size: 0.9rem;
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 0.6rem;
|
||||
background: var(--color-action-primary);
|
||||
color: var(--fs-text-on-action);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
font-weight: var(--fs-weight-medium);
|
||||
}
|
||||
.btn-submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
.btn-submit:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.auth-footer {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
|
||||
@@ -117,7 +117,7 @@ async function handleSubmit() {
|
||||
<p v-if="passwordMismatch" class="error-hint">Passwords do not match</p>
|
||||
</div>
|
||||
<p v-if="error" class="error-msg">{{ error }}</p>
|
||||
<button type="submit" class="btn-submit" :disabled="!canSubmit">
|
||||
<button type="submit" class="btn-primary btn-block" :disabled="!canSubmit">
|
||||
{{ submitting ? "Creating account..." : "Create Account" }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -216,24 +216,6 @@ async function handleSubmit() {
|
||||
font-size: 0.9rem;
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 0.6rem;
|
||||
background: var(--color-action-primary);
|
||||
color: var(--fs-text-on-action);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
font-weight: var(--fs-weight-medium);
|
||||
}
|
||||
.btn-submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
.btn-submit:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.auth-footer {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
|
||||
@@ -88,7 +88,7 @@ async function handleSubmit() {
|
||||
<p v-if="passwordMismatch" class="error-hint">Passwords do not match</p>
|
||||
</div>
|
||||
<p v-if="error" class="error-msg">{{ error }}</p>
|
||||
<button type="submit" class="btn-submit" :disabled="!canSubmit">
|
||||
<button type="submit" class="btn-primary btn-block" :disabled="!canSubmit">
|
||||
{{ submitting ? "Resetting..." : "Reset Password" }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -195,24 +195,6 @@ async function handleSubmit() {
|
||||
font-size: 0.9rem;
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 0.6rem;
|
||||
background: var(--color-action-primary);
|
||||
color: var(--fs-text-on-action);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
font-weight: var(--fs-weight-medium);
|
||||
}
|
||||
.btn-submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
.btn-submit:hover:not(:disabled) {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.auth-footer {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
|
||||
Reference in New Issue
Block a user