Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions frontend/src/lib/components/ArticleCardView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -481,17 +481,20 @@
</div>
{/if}
<div class="article-actions">
<!-- Save button -->
<!-- Save button. Label tracks state ("Save" → "Saved") and the bookmark
fills green, so the confirmation persists rather than relying on a
subtle color shift the user might miss. -->
<button
class="action-btn"
class:saved={isSaved}
title={isSaved ? 'Saved to read later. Tap to remove' : 'Save to read later'}
onclick={(e) => {
e.stopPropagation();
onToggleSave?.();
}}
>
<span class="action-icon"><Icon name="bookmark" size={16} /></span><span
class="action-label">Save</span
class="action-label">{isSaved ? 'Saved' : 'Save'}</span
>
</button>
<!-- Share: a toggle for the Blogs lane, surfaced in the bar so sharing is
Expand All @@ -504,7 +507,9 @@
class="action-btn share-btn"
class:saved={currentlyShared}
class:confirming={confirmingRemove}
title={currentlyShared ? 'Remove share' : shareRow?.createLabel}
title={currentlyShared
? 'Shared to your linkblog. Tap to remove'
: (shareRow?.createLabel ?? 'Share to your linkblog')}
onclick={(e) => {
e.stopPropagation();
if (currentlyShared) confirmingRemove = !confirmingRemove;
Expand Down Expand Up @@ -1822,12 +1827,14 @@
color: var(--color-primary, #0066cc);
}

/* Saved/Shared is a confirmed, positive state — success green (not the warning
amber it used to borrow), so "I've done the thing" reads at a glance. */
.action-btn.saved {
color: #ffc107;
color: var(--color-success, #4caf50);
}

.action-btn.saved:hover {
color: #ffc107;
color: var(--color-success, #4caf50);
}

.action-btn.active {
Expand Down
69 changes: 47 additions & 22 deletions frontend/src/lib/components/feed/ShareCommentBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
// Compared against the live prop so Save disables again right after a save.
let dirty = $derived(trimmed !== initialNote.trim());
let nearLimit = $derived(value.length > MAX - 200);
// A saved, at-rest note: show a persistent "Saved" confirmation in place of the
// (now hidden) Save control. Submitting blurs the field, so without this the
// checkmark would just vanish — leaving the user unsure the note landed.
let showSaved = $derived(!dirty && trimmed.length > 0);

function autosize() {
const el = textareaEl;
Expand Down Expand Up @@ -76,29 +80,36 @@
onfocus={() => (focused = true)}
onblur={() => (focused = false)}
></textarea>
<!-- Always rendered so it reserves its space — focusing reveals it via
opacity/visibility, never a layout shift. -->
<div class="actions" class:hidden={!focused}>
{#if nearLimit}
<span class="counter" class:over={value.length >= MAX}>{MAX - value.length}</span>
<!-- Always rendered so it reserves its space — focusing (or a saved note at
rest) reveals it via opacity/visibility, never a layout shift. -->
<div class="actions" class:hidden={!focused && !showSaved}>
{#if focused}
{#if nearLimit}
<span class="counter" class:over={value.length >= MAX}>{MAX - value.length}</span>
{/if}
<!-- preventDefault on mousedown keeps focus on the textarea so this click
lands before the blur that would otherwise hide the button. -->
<button
type="button"
class="btn btn-primary"
aria-label="Save"
onmousedown={(e) => e.preventDefault()}
onclick={submit}
disabled={!dirty}
>
<!-- Mobile collapses the label to the check icon to keep the row compact;
desktop keeps the word. -->
<Icon name="check" size={16} />
<span class="btn-text">Save</span>
</button>
{:else if showSaved}
<!-- Persistent confirmation: the note landed. Stays until the user edits
again, so the feedback doesn't blink out the moment they tap save. -->
<span class="saved-pill" aria-live="polite">
<Icon name="check" size={14} />
<span class="saved-text">Saved</span>
</span>
{/if}
<!-- preventDefault on mousedown keeps focus on the textarea so this click
lands before the blur that would otherwise hide the button. -->
<button
type="button"
class="btn btn-primary"
tabindex={focused ? 0 : -1}
aria-hidden={!focused}
aria-label="Save"
onmousedown={(e) => e.preventDefault()}
onclick={submit}
disabled={!dirty}
>
<!-- Mobile collapses the label to the check icon to keep the row compact;
desktop keeps the word. -->
<Icon name="check" size={16} />
<span class="btn-text">Save</span>
</button>
</div>
</div>
</section>
Expand Down Expand Up @@ -238,6 +249,20 @@
cursor: default;
}

/* At-rest confirmation that a note is saved — success green, quiet weight. */
.saved-pill {
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: var(--text-md);
font-weight: var(--weight-medium);
color: var(--color-success, #4caf50);
}

.saved-pill :global(.icon) {
flex-shrink: 0;
}

@media (prefers-reduced-motion: reduce) {
.actions {
transition: none;
Expand Down
Loading