mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Revamp login modal auth provider cards
This commit is contained in:
1 parent
d390f0c444
commit
616fad1612
8 files changed
+141
-37
No files matched your search
@@ -45,7 +45,9 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section class="space-y-4 rounded-2xl border border-border bg-overlay-panel-soft p-4 sm:p-5">
|
||||
<section
|
||||
class="space-y-5 rounded-3xl border border-border bg-overlay-panel-soft/90 p-4 shadow-lg backdrop-blur-sm sm:p-5"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<h3 class="text-base font-semibold text-text">Available methods</h3>
|
||||
<p class="text-sm text-text-muted">
|
||||
@@ -54,7 +56,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Login provider buttons are rendered dynamically -->
|
||||
<div data-login-providers class="grid gap-3 sm:grid-cols-2"></div>
|
||||
<div
|
||||
data-login-providers
|
||||
class="grid auto-rows-fr gap-4 sm:grid-cols-2 xl:grid-cols-3"
|
||||
></div>
|
||||
</section>
|
||||
|
||||
<div class="space-y-3">
|
||||
@@ -83,6 +88,10 @@
|
||||
>
|
||||
<span class="login-option__header">
|
||||
<span class="login-option__copy">
|
||||
<span
|
||||
data-provider-eyebrow
|
||||
class="login-option__eyebrow hidden"
|
||||
></span>
|
||||
<span data-provider-label class="login-option__label"></span>
|
||||
<span
|
||||
data-provider-description
|
||||
|
||||
+83
-25
@@ -589,76 +589,133 @@
|
||||
}
|
||||
|
||||
.login-option {
|
||||
@apply focus-ring relative flex w-full flex-col gap-3 rounded-2xl border border-border bg-overlay-panel-soft px-5 py-4 text-left text-text shadow-sm transition duration-base ease-in-out;
|
||||
@apply focus-ring relative flex h-full w-full flex-col gap-4 overflow-hidden border border-border bg-overlay-panel-soft px-6 py-5 text-left text-text shadow-lg transition duration-300 ease-out;
|
||||
border-radius: calc(var(--radius-xl) * 1.12);
|
||||
isolation: isolate;
|
||||
backdrop-filter: blur(var(--blur-modal-backdrop));
|
||||
background-image: linear-gradient(
|
||||
140deg,
|
||||
color-mix(in srgb, var(--surface-overlay-panel-soft) 88%, transparent) 0%,
|
||||
color-mix(in srgb, var(--surface-overlay-panel) 72%, transparent) 100%
|
||||
);
|
||||
transform: translate3d(0, 0, 0);
|
||||
transition-property: border-color, box-shadow, transform;
|
||||
}
|
||||
|
||||
.login-option:hover {
|
||||
@apply border-border-strong bg-overlay-panel shadow-md;
|
||||
.login-option::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
border-radius: inherit;
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 14%, 0);
|
||||
transition: opacity 220ms ease, transform 220ms ease;
|
||||
background: radial-gradient(
|
||||
115% 115% at 0% 0%,
|
||||
color-mix(in srgb, var(--status-neutral) 32%, transparent) 0%,
|
||||
transparent 65%
|
||||
);
|
||||
}
|
||||
|
||||
.login-option:hover,
|
||||
.login-option:focus-visible {
|
||||
@apply border-accent bg-overlay-panel shadow-md;
|
||||
@apply border-border-strong;
|
||||
box-shadow: var(--shadow-modal);
|
||||
transform: translate3d(0, -3px, 0);
|
||||
}
|
||||
|
||||
.login-option:hover::after,
|
||||
.login-option:focus-visible::after {
|
||||
opacity: 0.65;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.login-option:disabled,
|
||||
.login-option[aria-disabled="true"] {
|
||||
@apply cursor-not-allowed opacity-60 shadow-none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.login-option[data-tone="accent"]::after {
|
||||
background: radial-gradient(
|
||||
120% 120% at 0% 0%,
|
||||
color-mix(in srgb, var(--status-info) 38%, transparent) 0%,
|
||||
transparent 65%
|
||||
);
|
||||
}
|
||||
|
||||
.login-option[data-tone="warning"]::after {
|
||||
background: radial-gradient(
|
||||
120% 120% at 0% 0%,
|
||||
color-mix(in srgb, var(--status-warning) 42%, transparent) 0%,
|
||||
transparent 65%
|
||||
);
|
||||
}
|
||||
|
||||
.login-option--primary {
|
||||
@apply shadow-sm;
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
.login-option--ghost {
|
||||
@apply bg-transparent shadow-none;
|
||||
}
|
||||
|
||||
.login-option--ghost:hover,
|
||||
.login-option--ghost:focus-visible {
|
||||
@apply bg-overlay-panel-soft shadow-sm;
|
||||
background-image: linear-gradient(
|
||||
145deg,
|
||||
color-mix(in srgb, var(--surface-overlay-panel-soft) 82%, transparent) 0%,
|
||||
color-mix(in srgb, var(--surface-overlay-panel-soft) 68%, transparent) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.login-option--outline {
|
||||
@apply bg-transparent border-border-strong;
|
||||
}
|
||||
|
||||
.login-option--outline:hover,
|
||||
.login-option--outline:focus-visible {
|
||||
@apply bg-overlay-panel-soft shadow-sm;
|
||||
@apply border-border-strong;
|
||||
}
|
||||
|
||||
.login-option--link {
|
||||
@apply border border-transparent bg-transparent text-accent shadow-none;
|
||||
}
|
||||
|
||||
.login-option--link:hover,
|
||||
.login-option--link:focus-visible {
|
||||
@apply text-accent-strong underline;
|
||||
.login-option__header,
|
||||
.login-option__copy,
|
||||
.login-option__status,
|
||||
.login-option__capabilities {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.login-option__header {
|
||||
@apply flex w-full items-start justify-between gap-3;
|
||||
@apply flex w-full items-start justify-between gap-4;
|
||||
}
|
||||
|
||||
.login-option__copy {
|
||||
@apply flex min-w-0 flex-col items-start gap-1;
|
||||
@apply flex min-w-0 flex-col items-start gap-2;
|
||||
}
|
||||
|
||||
.login-option__eyebrow {
|
||||
@apply text-[0.7rem] font-semibold uppercase text-text-muted;
|
||||
letter-spacing: 0.18em;
|
||||
}
|
||||
|
||||
.login-option:hover .login-option__eyebrow,
|
||||
.login-option:focus-visible .login-option__eyebrow {
|
||||
@apply text-text;
|
||||
}
|
||||
|
||||
.login-option__label {
|
||||
@apply text-base font-semibold text-text;
|
||||
@apply text-lg font-semibold text-text;
|
||||
}
|
||||
|
||||
.login-option__description {
|
||||
@apply text-sm text-text-muted;
|
||||
@apply text-sm leading-relaxed text-text-muted;
|
||||
}
|
||||
|
||||
.login-option__chevron {
|
||||
@apply inline-flex h-8 w-8 items-center justify-center rounded-full bg-panel text-text-muted transition duration-base ease-in-out;
|
||||
@apply inline-flex h-9 w-9 items-center justify-center rounded-full bg-overlay-panel text-text-muted transition duration-200 ease-out;
|
||||
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-overlay) 60%, transparent);
|
||||
}
|
||||
|
||||
.login-option:hover .login-option__chevron,
|
||||
.login-option:focus-visible .login-option__chevron {
|
||||
@apply bg-accent text-white;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.login-option__status {
|
||||
@@ -667,6 +724,7 @@
|
||||
|
||||
.login-option__capabilities {
|
||||
@apply flex flex-wrap gap-2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.btn-ghost[data-state="active"],
|
||||
|
||||
@@ -59,8 +59,8 @@ if (!Object.prototype.hasOwnProperty.call(providerMap, "nip07")) {
|
||||
const builtInMetadata = [
|
||||
{
|
||||
id: "nsec",
|
||||
label: "Direct key",
|
||||
description: "Use a saved nsec key stored locally.",
|
||||
label: "nsec or seed (direct private key)",
|
||||
description: "Use a saved private key stored locally.",
|
||||
badgeVariant: "warning",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const PROVIDER_ID = "nip07";
|
||||
const PROVIDER_LABEL = "Login with Extension";
|
||||
const PROVIDER_LABEL = "extension (nip-07)";
|
||||
const PROVIDER_DESCRIPTION =
|
||||
"Use a NIP-07 compatible browser extension to authenticate.";
|
||||
"Authorize with a NIP-07 browser extension like Alby or nos2x.";
|
||||
const PROVIDER_CAPABILITIES = Object.freeze([
|
||||
Object.freeze({
|
||||
id: "signing",
|
||||
@@ -47,6 +47,8 @@ export default {
|
||||
id: PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
description: PROVIDER_DESCRIPTION,
|
||||
eyebrow: "Recommended",
|
||||
tone: "accent",
|
||||
badgeVariant: "info",
|
||||
capabilities: PROVIDER_CAPABILITIES,
|
||||
button: PROVIDER_BUTTON,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const PROVIDER_ID = "nip46";
|
||||
const PROVIDER_LABEL = "Remote signer";
|
||||
const PROVIDER_DESCRIPTION = "Connect to a NIP-46 compatible signer via Nostr Connect.";
|
||||
const PROVIDER_LABEL = "remote signer (nip-46)";
|
||||
const PROVIDER_DESCRIPTION =
|
||||
"Connect to a remote signer via Nostr Connect for hardware or mobile approvals.";
|
||||
const PROVIDER_CAPABILITIES = Object.freeze([
|
||||
Object.freeze({
|
||||
id: "signing",
|
||||
@@ -63,6 +64,8 @@ export default {
|
||||
id: PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
description: PROVIDER_DESCRIPTION,
|
||||
eyebrow: "Nostr Connect",
|
||||
tone: "accent",
|
||||
badgeVariant: "info",
|
||||
capabilities: PROVIDER_CAPABILITIES,
|
||||
button: PROVIDER_BUTTON,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const PROVIDER_ID = "nsec";
|
||||
const PROVIDER_LABEL = "Direct key";
|
||||
const PROVIDER_DESCRIPTION = "Import an nsec or mnemonic seed.";
|
||||
const PROVIDER_LABEL = "nsec or seed (direct private key)";
|
||||
const PROVIDER_DESCRIPTION =
|
||||
"Paste an nsec, hex key, or BIP-39 seed. Keep it encrypted on this device if you choose.";
|
||||
const PROVIDER_CAPABILITIES = Object.freeze([
|
||||
Object.freeze({
|
||||
id: "signing",
|
||||
@@ -64,6 +65,8 @@ export default {
|
||||
id: PROVIDER_ID,
|
||||
label: PROVIDER_LABEL,
|
||||
description: PROVIDER_DESCRIPTION,
|
||||
eyebrow: "Advanced",
|
||||
tone: "warning",
|
||||
badgeVariant: "warning",
|
||||
capabilities: PROVIDER_CAPABILITIES,
|
||||
button: PROVIDER_BUTTON,
|
||||
|
||||
@@ -40,11 +40,23 @@ function normalizeProvider(provider, fallbackId, index) {
|
||||
? provider.label.trim()
|
||||
: rawId;
|
||||
|
||||
const eyebrow =
|
||||
typeof provider.eyebrow === "string" && provider.eyebrow.trim()
|
||||
? provider.eyebrow.trim()
|
||||
: "";
|
||||
|
||||
const description =
|
||||
typeof provider.description === "string" && provider.description.trim()
|
||||
? provider.description.trim()
|
||||
: "";
|
||||
|
||||
const rawTone =
|
||||
typeof provider.tone === "string" && provider.tone.trim()
|
||||
? provider.tone.trim().toLowerCase()
|
||||
: "";
|
||||
|
||||
const tone = rawTone ? rawTone : "";
|
||||
|
||||
const button = provider.button && typeof provider.button === "object"
|
||||
? provider.button
|
||||
: {};
|
||||
@@ -66,11 +78,13 @@ function normalizeProvider(provider, fallbackId, index) {
|
||||
id: rawId,
|
||||
source: provider,
|
||||
label,
|
||||
eyebrow,
|
||||
description,
|
||||
button,
|
||||
messages,
|
||||
capabilities,
|
||||
order,
|
||||
tone,
|
||||
disabled: provider.disabled === true || provider.available === false,
|
||||
errorMessage:
|
||||
typeof provider.errorMessage === "string" && provider.errorMessage.trim()
|
||||
@@ -949,6 +963,12 @@ export default class LoginModalController {
|
||||
provider.button?.variant,
|
||||
provider.button?.className,
|
||||
);
|
||||
if (provider.tone) {
|
||||
button.dataset.tone = provider.tone;
|
||||
} else {
|
||||
delete button.dataset.tone;
|
||||
button.removeAttribute("data-tone");
|
||||
}
|
||||
button.disabled = provider.disabled;
|
||||
if (provider.disabled) {
|
||||
button.setAttribute("aria-disabled", "true");
|
||||
@@ -956,6 +976,12 @@ export default class LoginModalController {
|
||||
button.removeAttribute("aria-disabled");
|
||||
}
|
||||
|
||||
const eyebrowNode = button.querySelector("[data-provider-eyebrow]");
|
||||
if (eyebrowNode instanceof HTMLElement) {
|
||||
eyebrowNode.textContent = provider.eyebrow || "";
|
||||
eyebrowNode.classList.toggle("hidden", !provider.eyebrow);
|
||||
}
|
||||
|
||||
const labelNode = button.querySelector("[data-provider-label]");
|
||||
if (labelNode instanceof HTMLElement) {
|
||||
labelNode.textContent = provider.label;
|
||||
|
||||
@@ -582,14 +582,17 @@ for (const _ of [0]) {
|
||||
assert.equal(extensionButton.dataset.providerId, 'nip07');
|
||||
const extensionLabel = extensionButton.querySelector('[data-provider-variant]');
|
||||
assert.ok(extensionLabel, 'extension entry should include a provider badge');
|
||||
assert.equal(extensionLabel.textContent, 'Login with Extension');
|
||||
assert.equal(extensionLabel.textContent, 'extension (nip-07)');
|
||||
assert.equal(extensionLabel.dataset.providerVariant, 'info');
|
||||
assert.equal(extensionButton.getAttribute('aria-pressed'), 'false');
|
||||
|
||||
assert.equal(directKeyButton.dataset.providerId, 'nsec');
|
||||
const directKeyLabel = directKeyButton.querySelector('[data-provider-variant]');
|
||||
assert.ok(directKeyLabel, 'direct key entry should include a provider badge');
|
||||
assert.equal(directKeyLabel.textContent, 'Direct key');
|
||||
assert.equal(
|
||||
directKeyLabel.textContent,
|
||||
'nsec or seed (direct private key)',
|
||||
);
|
||||
assert.equal(directKeyLabel.dataset.providerVariant, 'warning');
|
||||
assert.equal(directKeyButton.getAttribute('aria-pressed'), 'false');
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user