feat(site-builder): page duplicate/reorder/set-landing + fix cross-page node copy/paste

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 07:36:00 -07:00
co-authored by Claude Opus 4.8
parent 204ea5e078
commit a698f014b0
9 changed files with 1040 additions and 108 deletions
+78 -40
View File
@@ -15,6 +15,9 @@ export const PagesPanel: React.FC = () => {
addPage,
deletePage,
renamePage,
duplicatePage,
movePage,
setLandingPage,
updatePageSeo,
} = usePages();
const [isAdding, setIsAdding] = useState(false);
@@ -68,6 +71,33 @@ export const PagesPanel: React.FC = () => {
* differently-colored category. The active/editing state reuses the same
* accent-outline treatment the page list already uses for the active page,
* so there's one consistent "this is what's currently open" affordance. */
/* ---------- Per-page-row icon button ----------
* Shared 24x24 icon-button styling used by every action in the page row
* (SEO settings, rename, duplicate, reorder, set-as-home, delete) so a new
* action slots in looking identical to the pre-existing gear/pencil/trash
* buttons -- same dark-theme neutral treatment, `disabled` dims the icon
* (matching how `disabled` already reads elsewhere in this panel, e.g. the
* "Add Page" button), and `danger` reuses the existing delete-button
* accent color. */
const pageActionBtnStyle = (opts: { disabled?: boolean; danger?: boolean } = {}): React.CSSProperties => ({
width: 24,
height: 24,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 11,
color: opts.disabled
? 'var(--color-text-dim)'
: opts.danger
? 'var(--color-danger)'
: 'var(--color-text-muted)',
background: 'transparent',
border: 'none',
borderRadius: 'var(--radius-sm)',
cursor: opts.disabled ? 'default' : 'pointer',
opacity: opts.disabled ? 0.5 : 1,
});
const zoneRowStyle = (isActive: boolean): React.CSSProperties => ({
display: 'flex',
alignItems: 'center',
@@ -344,26 +374,58 @@ export const PagesPanel: React.FC = () => {
</div>
</div>
<div
style={{ display: 'flex', gap: 4, flexShrink: 0 }}
style={{ display: 'flex', gap: 2, flexShrink: 0, flexWrap: 'wrap', justifyContent: 'flex-end', maxWidth: 96 }}
onClick={(e) => e.stopPropagation()}
>
<button
onClick={() => movePage(page.id, 'up')}
disabled={pageIndex === 0}
data-tooltip="Move up"
aria-label={`Move ${page.name} up`}
style={pageActionBtnStyle({ disabled: pageIndex === 0 })}
>
<i className="fa fa-arrow-up" aria-hidden="true" />
</button>
<button
onClick={() => movePage(page.id, 'down')}
disabled={pageIndex === pages.length - 1}
data-tooltip="Move down"
aria-label={`Move ${page.name} down`}
style={pageActionBtnStyle({ disabled: pageIndex === pages.length - 1 })}
>
<i className="fa fa-arrow-down" aria-hidden="true" />
</button>
<button
onClick={() => duplicatePage(page.id)}
data-tooltip="Duplicate"
aria-label={`Duplicate ${page.name}`}
style={pageActionBtnStyle()}
>
<i className="fa fa-clone" aria-hidden="true" />
</button>
{isLanding ? (
<span
data-tooltip="This is the home page"
aria-label={`${page.name} is the home page`}
style={pageActionBtnStyle({ disabled: true })}
>
<i className="fa fa-home" aria-hidden="true" />
</span>
) : (
<button
onClick={() => setLandingPage(page.id)}
data-tooltip="Set as home page"
aria-label={`Set ${page.name} as the home page`}
style={pageActionBtnStyle()}
>
<i className="fa fa-home" aria-hidden="true" />
</button>
)}
<button
onClick={() => setSeoSettingsPageId(page.id)}
data-tooltip="Page Settings (SEO)"
aria-label={`Page settings for ${page.name}`}
style={{
width: 24,
height: 24,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 11,
color: 'var(--color-text-muted)',
background: 'transparent',
border: 'none',
borderRadius: 'var(--radius-sm)',
cursor: 'pointer',
}}
style={pageActionBtnStyle()}
>
<i className="fa fa-cog" aria-hidden="true" />
</button>
@@ -371,19 +433,7 @@ export const PagesPanel: React.FC = () => {
onClick={() => startEditing(page)}
data-tooltip="Rename"
aria-label={`Rename ${page.name}`}
style={{
width: 24,
height: 24,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 11,
color: 'var(--color-text-muted)',
background: 'transparent',
border: 'none',
borderRadius: 'var(--radius-sm)',
cursor: 'pointer',
}}
style={pageActionBtnStyle()}
>
<i className="fa fa-pencil" aria-hidden="true" />
</button>
@@ -392,19 +442,7 @@ export const PagesPanel: React.FC = () => {
onClick={() => setDeleteConfirmId(page.id)}
data-tooltip="Delete"
aria-label={`Delete ${page.name}`}
style={{
width: 24,
height: 24,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 11,
color: 'var(--color-text-muted)',
background: 'transparent',
border: 'none',
borderRadius: 'var(--radius-sm)',
cursor: 'pointer',
}}
style={pageActionBtnStyle({ danger: true })}
>
<i className="fa fa-trash" aria-hidden="true" />
</button>