Skip to content
Draft
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
60 changes: 20 additions & 40 deletions src/admin/pages/site/toolbar/PublishActionGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { cn } from '@ui/cn'
import { SplitButton, type SplitButtonMenuItem } from '@ui/components/SplitButton'
import type { IconComponent } from 'pixel-art-icons/types'
import styles from './Toolbar.module.css'
Expand All @@ -23,10 +22,6 @@ interface PublishActionGroupProps {
menuItems: PublishActionMenuItem[]
menuLabel?: string
triggerLabel?: string
toast?: {
tone: 'status' | 'alert'
message: string
} | null
}

export function PublishActionGroup({
Expand All @@ -44,7 +39,6 @@ export function PublishActionGroup({
menuItems,
menuLabel = 'Publishing actions',
triggerLabel = 'More publishing actions',
toast,
}: PublishActionGroupProps) {
return (
<div className={styles.publishActionGroup}>
Expand All @@ -61,40 +55,26 @@ export function PublishActionGroup({
</span>
)}

<div className={styles.publishActionWrapper}>
<SplitButton
variant={publishState === 'error' ? 'destructive' : 'primary'}
size="sm"
label={publishLabel}
icon={publishIcon}
onClick={onPublish}
disabled={publishDisabled}
busy={publishBusy}
primaryAriaLabel={publishAriaLabel}
primaryTooltip={publishTitle}
primaryState={publishState}
primaryClassName={styles.publishPrimaryButton}
triggerClassName={styles.publishMenuTrigger}
menuItems={menuItems}
menuLabel={menuLabel}
menuTriggerLabel={triggerLabel}
primaryTestId="toolbar-publish-btn"
menuTriggerTestId="toolbar-publish-actions-trigger"
menuTestId="toolbar-publish-actions-menu"
/>

{toast && (
<div
role={toast.tone === 'alert' ? 'alert' : 'status'}
className={cn(
styles.publishToast,
toast.tone === 'status' && styles.publishToastStatus,
)}
>
{toast.message}
</div>
)}
</div>
<SplitButton
variant={publishState === 'error' ? 'destructive' : 'primary'}
size="sm"
label={publishLabel}
icon={publishIcon}
onClick={onPublish}
disabled={publishDisabled}
busy={publishBusy}
primaryAriaLabel={publishAriaLabel}
primaryTooltip={publishTitle}
primaryState={publishState}
primaryClassName={styles.publishPrimaryButton}
triggerClassName={styles.publishMenuTrigger}
menuItems={menuItems}
menuLabel={menuLabel}
menuTriggerLabel={triggerLabel}
primaryTestId="toolbar-publish-btn"
menuTriggerTestId="toolbar-publish-actions-trigger"
menuTestId="toolbar-publish-actions-menu"
/>
</div>
)
}
35 changes: 9 additions & 26 deletions src/admin/pages/site/toolbar/PublishButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { SaveSolidIcon } from 'pixel-art-icons/icons/save-solid'
import { StepUpCancelledMessage, useStepUp } from '@admin/shared/StepUp'
import { SchedulePublishDialog } from '@admin/modals/SchedulePublishDialog'
import type { PersistenceSaveStatus } from '@site/hooks/usePersistence'
import { pushToast } from '@ui/components/Toast'
import { PublishActionGroup, type PublishActionMenuItem } from './PublishActionGroup'
import { getErrorMessage } from '@core/utils/errorMessage'

Expand Down Expand Up @@ -44,7 +45,6 @@ export function PublishButton({ enabled = true, onSave, saveStatus }: PublishBut
const hasUnsavedChanges = useEditorStore((s) => s.hasUnsavedChanges)
const { runStepUp } = useStepUp()
const [state, setState] = useState<PublishState>('idle')
const [message, setMessage] = useState<string | null>(null)
const [isSaving, setIsSaving] = useState(false)
const [scheduleDialogOpen, setScheduleDialogOpen] = useState(false)
const statusTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
Expand All @@ -67,7 +67,6 @@ export function PublishButton({ enabled = true, onSave, saveStatus }: PublishBut
if (cancelled) return
if (status.draftMatchesPublished) {
setState('published')
setMessage(null)
}
} catch (err) {
console.warn('[toolbar] Failed to load publish status:', err)
Expand All @@ -84,7 +83,6 @@ export function PublishButton({ enabled = true, onSave, saveStatus }: PublishBut
statusTimerRef.current = null
const resetTimer = setTimeout(() => {
setState('idle')
setMessage(null)
}, 0)
return () => clearTimeout(resetTimer)
}, [hasUnsavedChanges, state])
Expand All @@ -93,15 +91,6 @@ export function PublishButton({ enabled = true, onSave, saveStatus }: PublishBut
if (statusTimerRef.current) clearTimeout(statusTimerRef.current)
statusTimerRef.current = setTimeout(() => {
setState('idle')
setMessage(null)
statusTimerRef.current = null
}, 5000)
}

const clearMessageLater = () => {
if (statusTimerRef.current) clearTimeout(statusTimerRef.current)
statusTimerRef.current = setTimeout(() => {
setMessage(null)
statusTimerRef.current = null
}, 5000)
}
Expand All @@ -115,7 +104,6 @@ export function PublishButton({ enabled = true, onSave, saveStatus }: PublishBut
}

setState('publishing')
setMessage(null)

try {
await onSave?.()
Expand All @@ -125,25 +113,24 @@ export function PublishButton({ enabled = true, onSave, saveStatus }: PublishBut
// blast-radius site action (one click replaces every public page),
// which is why the server gates it behind a fresh step-up window
// in addition to the `pages.publish` capability check.
const result = await runStepUp(() => publishCmsDraft())
await runStepUp(() => publishCmsDraft())
setState('published')
setMessage(
result.publishedPages === 1
? '1 page published'
: `${result.publishedPages} pages published`,
)
clearMessageLater()
} catch (err) {
if (err instanceof Error && err.message === StepUpCancelledMessage) {
// User dismissed the step-up dialog — return the button to its
// resting state without surfacing an error message; this is the
// same UX every other step-up-gated action uses.
setState('idle')
setMessage(null)
return
}
console.error('[toolbar] Publish failed:', err)
setState('error')
setMessage(getErrorMessage(err, 'Unknown publish error'))
pushToast({
kind: 'error',
title: 'Publish failed',
body: getErrorMessage(err, 'Unknown publish error'),
location: 'site-editor',
})
resetErrorLater()
}
}
Expand Down Expand Up @@ -236,10 +223,6 @@ export function PublishButton({ enabled = true, onSave, saveStatus }: PublishBut
publishIcon={PublishIcon}
onPublish={handlePublish}
menuItems={menuItems}
toast={message ? {
tone: state === 'error' ? 'alert' : 'status',
message,
} : null}
/>
{activePage && (
<SchedulePublishDialog
Expand Down
25 changes: 0 additions & 25 deletions src/admin/pages/site/toolbar/Toolbar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,6 @@
background: var(--danger-light);
}

.publishActionWrapper {
position: relative;
height: 28px;
}

/* Publish-specific overrides on the shared SplitButton halves: a stable min
* width so the label can swap (Publish → Publishing → Published) without the
* control reflowing, a slightly slimmer chevron trigger, and a softened
Expand All @@ -257,26 +252,6 @@
width: 26px;
}

.publishToast {
position: absolute;
top: calc(100% + 6px);
right: 0;
background: var(--bg-surface);
border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent);
border-radius: 6px;
padding: var(--space-xs) var(--space-m);
font-size: var(--text-xs);
color: var(--danger-lighter);
white-space: nowrap;
z-index: 201;
box-shadow: 0 4px 12px var(--scrim-40);
}

.publishToastStatus {
border-color: color-mix(in srgb, var(--success) 35%, transparent);
color: var(--success);
}

@media (max-width: 760px) {
.header {
height: 68px;
Expand Down
Loading