Buttons: primary, subtle and one danger; the restarts ask first (#316)

Cancel is the quiet button next to a filled one, in every dialog; Decline on an invitation is quiet too. There is one filled red danger button, with the outline version in theme.css gone. Restart Tor and Restart daemon ask again in place.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
2026-10-08 14:53:09 -04:00
co-authored by Claude Sonnet 5.5
parent 3a7677ef16
commit 2c8609ca98
10 changed files with 98 additions and 27 deletions
+12
View File
@@ -569,6 +569,18 @@ const SCENES = [
},
clip: { x: 0, y: 380, width: 280, height: 260 }
},
{
// Restarting Tor asks first (#316).
name: 'settings-tor-confirm',
setup: async (p) => {
await p.evaluate(`window.__mohoShots.store.setActivePanel('settings')`)
await sleep(300)
await p.evaluate(click('.settings-rail-item', 'Tor'))
await sleep(300)
await p.evaluate(click('button', 'Restart Tor from scratch'))
await sleep(300)
}
},
// Motion, recorded rather than photographed.
{
name: 'motion-hover-rows',
+6 -5
View File
@@ -2488,11 +2488,12 @@ img.composer-emoji {
outline-offset: -10px;
}
/* The one button in the app that destroys something. Not --error, which is a
pale text colour and reads as decorative when used as a fill. */
.button.danger {
background: var(--danger);
color: var(--on-accent);
/* Asking again, in place of the button that was pressed. */
.confirm-button {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: var(--spacing-s);
}
/* --- mentions ---------------------------------------------------------- */
@@ -488,7 +488,7 @@ function AccountRow({ account }: { account: Account }): JSX.Element {
>
<Icon name="delete" size={16} /> Remove
</button>
<button type="button" className="button" autoFocus onClick={() => setConfirmingRemove(false)}>
<button type="button" className="button subtle" autoFocus onClick={() => setConfirmingRemove(false)}>
Keep it
</button>
</div>
@@ -0,0 +1,58 @@
import { useEffect, useState } from 'react'
/**
* A button for something that interrupts - restarting Tor, restarting the
* daemon - that asks once more before doing it, in place.
*
* Not a dialog: the question is about the thing in front of you and answers
* itself in a click, and it takes itself back after a few seconds so a
* forgotten question is not left lying about as a loaded button.
*/
export function ConfirmButton({
label,
question,
confirmLabel,
onConfirm,
danger
}: {
label: string
/** What is being asked, in a few words: "Restart Tor and lose its circuits?" */
question: string
confirmLabel: string
onConfirm: () => void
danger?: boolean
}): JSX.Element {
const [asking, setAsking] = useState(false)
useEffect(() => {
if (!asking) return
const t = setTimeout(() => setAsking(false), 6000)
return () => clearTimeout(t)
}, [asking])
if (!asking) {
return (
<button type="button" className="button subtle" onClick={() => setAsking(true)}>
{label}
</button>
)
}
return (
<div className="confirm-button" role="alertdialog" aria-label={question}>
<span className="small">{question}</span>
<button
type="button"
className={danger ? 'button danger' : 'button'}
onClick={() => {
setAsking(false)
onConfirm()
}}
>
{confirmLabel}
</button>
<button type="button" className="button subtle" autoFocus onClick={() => setAsking(false)}>
Cancel
</button>
</div>
)
}
+1 -1
View File
@@ -162,7 +162,7 @@ export function ExportDialog({
)}
<div className="reason-prompt-actions">
<button type="button" className="button" onClick={onCancel}>
<button type="button" className="button subtle" onClick={onCancel}>
Cancel
</button>
<button
+1 -1
View File
@@ -44,7 +44,7 @@ export function InvitePanel({ groupId }: { groupId: string }): JSX.Element | nul
<button type="button" className="button primary" onClick={() => answer(true)}>
Accept
</button>
<button type="button" className="button danger" onClick={() => answer(false)}>
<button type="button" className="button subtle" onClick={() => answer(false)}>
Decline
</button>
</div>
+1 -1
View File
@@ -50,7 +50,7 @@ export function LeaveConfirm(props: {
</div>
<p className="small">{detail}</p>
<div className="leave-confirm-actions">
<button type="button" className="button" onClick={onCancel} autoFocus>
<button type="button" className="button subtle" onClick={onCancel} autoFocus>
Cancel
</button>
<button
+1 -1
View File
@@ -74,7 +74,7 @@ export function ReasonPrompt({
}}
/>
<div className="reason-prompt-actions">
<button type="button" className="button" onClick={onCancel}>
<button type="button" className="button subtle" onClick={onCancel}>
Cancel
</button>
<button
@@ -1,4 +1,5 @@
import { useEffect, useMemo, useState } from 'react'
import { ConfirmButton } from '../ConfirmButton'
import {
ChoiceSetting,
DirectorySetting,
@@ -941,15 +942,14 @@ function TorSettings(): JSX.Element {
>
Regenerate circuit
</button>
<button
type="button"
className="button subtle"
onClick={() =>
<ConfirmButton
label="Restart Tor from scratch"
question="Drop every Tor connection and start again?"
confirmLabel="Restart Tor"
onConfirm={() =>
call('restartTorFromScratch', {}, 'Restarting Tor from scratch - this can take a minute…')
}
>
Restart Tor from scratch
</button>
/>
</div>
</SettingsSection>
</>
@@ -1173,16 +1173,15 @@ function AboutSettings(): JSX.Element {
{linkUp ? 'connected' : 'not connected'}
</span>
</div>
<button
type="button"
className="button subtle"
onClick={() => {
<ConfirmButton
label="Restart daemon"
question="Disconnect everything and restart nobilis?"
confirmLabel="Restart"
onConfirm={() => {
void window.moho.restartDaemon()
store.toast('info', 'Restarting nobilis\u2026')
}}
>
Restart daemon
</button>
/>
{status && !status.available && (
<p className="small" style={{ color: 'var(--warning)' }}>
The nobilis binary wasn&apos;t found. Run <code>cargo build --release</code>, or start
+4 -3
View File
@@ -376,9 +376,10 @@ a:hover {
}
.button.danger {
background: transparent;
color: var(--error);
border: 1px solid var(--outline-medium);
/* The one button that destroys something: filled red, white text. Not
--error, a pale pink for text that went wrong, which cannot be a fill. */
background: var(--danger);
color: var(--on-accent);
}
.muted {