WIP PWA for Grain
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add login dialog component

+129
+129
src/components/molecules/grain-login-dialog.js
··· 1 + import { LitElement, html, css } from 'lit'; 2 + 3 + export class GrainLoginDialog extends LitElement { 4 + static properties = { 5 + _open: { state: true }, 6 + _handle: { state: true }, 7 + _loading: { state: true } 8 + }; 9 + 10 + static styles = css` 11 + :host { 12 + display: contents; 13 + } 14 + .overlay { 15 + position: fixed; 16 + top: 0; 17 + left: 0; 18 + right: 0; 19 + bottom: 0; 20 + background: rgba(0, 0, 0, 0.8); 21 + display: flex; 22 + align-items: center; 23 + justify-content: center; 24 + z-index: 1000; 25 + } 26 + .dialog { 27 + background: var(--color-bg-primary); 28 + border-radius: 12px; 29 + padding: var(--space-lg); 30 + width: 90%; 31 + max-width: 320px; 32 + } 33 + h2 { 34 + margin: 0 0 var(--space-md); 35 + font-size: var(--font-size-md); 36 + font-weight: var(--font-weight-semibold); 37 + color: var(--color-text-primary); 38 + } 39 + input { 40 + width: 100%; 41 + padding: var(--space-sm); 42 + border: 1px solid var(--color-border); 43 + border-radius: 6px; 44 + font-size: var(--font-size-sm); 45 + background: var(--color-bg-primary); 46 + color: var(--color-text-primary); 47 + margin-bottom: var(--space-md); 48 + box-sizing: border-box; 49 + } 50 + input:focus { 51 + outline: none; 52 + border-color: var(--color-text-primary); 53 + } 54 + button { 55 + width: 100%; 56 + padding: var(--space-sm); 57 + background: var(--color-text-primary); 58 + color: var(--color-bg-primary); 59 + border: none; 60 + border-radius: 6px; 61 + font-size: var(--font-size-sm); 62 + font-weight: var(--font-weight-semibold); 63 + cursor: pointer; 64 + } 65 + button:disabled { 66 + opacity: 0.5; 67 + cursor: not-allowed; 68 + } 69 + `; 70 + 71 + constructor() { 72 + super(); 73 + this._open = false; 74 + this._handle = ''; 75 + this._loading = false; 76 + } 77 + 78 + open() { 79 + this._open = true; 80 + this._handle = ''; 81 + this._loading = false; 82 + } 83 + 84 + close() { 85 + this._open = false; 86 + this._handle = ''; 87 + this._loading = false; 88 + } 89 + 90 + #handleSubmit(e) { 91 + e.preventDefault(); 92 + if (!this._handle.trim()) return; 93 + 94 + this._loading = true; 95 + this.dispatchEvent(new CustomEvent('login', { 96 + detail: { handle: this._handle.trim() } 97 + })); 98 + } 99 + 100 + #handleOverlayClick(e) { 101 + if (e.target === e.currentTarget) { 102 + this.close(); 103 + } 104 + } 105 + 106 + render() { 107 + if (!this._open) return null; 108 + 109 + return html` 110 + <div class="overlay" @click=${this.#handleOverlayClick}> 111 + <form class="dialog" @submit=${this.#handleSubmit}> 112 + <h2>Login with Bluesky</h2> 113 + <input 114 + type="text" 115 + placeholder="handle.bsky.social" 116 + .value=${this._handle} 117 + @input=${e => this._handle = e.target.value} 118 + ?disabled=${this._loading} 119 + /> 120 + <button type="submit" ?disabled=${this._loading || !this._handle.trim()}> 121 + ${this._loading ? 'Redirecting...' : 'Continue'} 122 + </button> 123 + </form> 124 + </div> 125 + `; 126 + } 127 + } 128 + 129 + customElements.define('grain-login-dialog', GrainLoginDialog);