managarten/packages/website-blocks/src/columns/ColumnsInspector.svelte
Till JS 7a4f8894e1 feat(website): M3 — 5 more blocks, containers, upload, themes
Expands the builder from 3 M1 blocks to 8. Containers (columns) and
media blocks (image, gallery) are the structural additions; cta and faq
round out the content coverage.

packages/website-blocks:
- image, cta, faq, columns (container), gallery — each with Zod schema,
  renderer (mode-aware for edit/preview/public), and fallback inspector.
- Block type extended with optional `children` + `renderChild` snippet
  so containers render their children through the same chrome the
  outer renderer provides (click-to-select, public-path tagging).
- themes/: 3 presets (classic light, modern dark, warm) with
  `resolveTheme` + `themeCssVars` helpers. Public layout now emits
  CSS vars via `style=` on the root; block components read
  `var(--wb-primary)` / `var(--wb-bg)` / `var(--wb-fg)` / etc.
- Registry updated; new exports + `./themes` subpath export.

apps/mana/apps/web/src/lib/modules/website:
- upload.ts: multipart POST to mana-media with `app=website` scope,
  returns { mediaId, url }. 25 MB cap, non-image rejection client-side.
- components/ImageInspector + GalleryInspector: app-side overrides
  wired to upload. Registered via `CUSTOM_INSPECTORS` in BlockInspector
  so block.type → app-side inspector, fallback to registry otherwise.
- components/SiteSettingsDialog: theme preset picker + color overrides
  for primary/bg/fg + footer text. Mounted from a ⚙ button in the
  editor's left pane.
- components/BlockRenderer: rebuilt around a byParent map + recursive
  `renderBlock` snippet so container blocks can render their children
  through the same click-to-select wrapper as top-level blocks.
- routes/s/[siteSlug]: rename `[[...path]]` → `[...path]` (SvelteKit
  treats rest segments as optional automatically — double-bracket form
  errored at sync time). +page.svelte renders snapshot trees
  recursively so published pages match the editor.

apps/api: unchanged.

Validation:
- pnpm run validate:all: all 6 gates green
- pnpm run check (web): 0 errors, 0 warnings
- apps/api type-check: green
- website-blocks tsc: green

Plan: docs/plans/website-builder.md (M3 block shipped)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:27:49 +02:00

107 lines
2.5 KiB
Svelte

<script lang="ts">
import type { BlockInspectorProps } from '../types';
import type { ColumnsProps } from './schema';
let { block, onChange }: BlockInspectorProps<ColumnsProps> = $props();
</script>
<div class="wb-inspector">
<label class="wb-field">
<span>Spalten-Anzahl</span>
<select
value={String(block.props.count)}
onchange={(e) => onChange({ count: Number(e.currentTarget.value) as ColumnsProps['count'] })}
>
<option value="2">2 Spalten</option>
<option value="3">3 Spalten</option>
</select>
</label>
<div class="wb-row">
<label class="wb-field">
<span>Abstand</span>
<select
value={block.props.gap}
onchange={(e) => onChange({ gap: e.currentTarget.value as ColumnsProps['gap'] })}
>
<option value="sm">Klein</option>
<option value="md">Mittel</option>
<option value="lg">Groß</option>
</select>
</label>
<label class="wb-field">
<span>Vertikale Ausrichtung</span>
<select
value={block.props.align}
onchange={(e) => onChange({ align: e.currentTarget.value as ColumnsProps['align'] })}
>
<option value="start">Oben</option>
<option value="center">Mittig</option>
<option value="stretch">Gestreckt</option>
</select>
</label>
</div>
<label class="wb-checkbox">
<input
type="checkbox"
checked={block.props.stackOnMobile}
onchange={(e) => onChange({ stackOnMobile: e.currentTarget.checked })}
/>
<span>Auf Mobile untereinander stapeln</span>
</label>
<p class="wb-hint">
Blöcke in eine Spalte hinzufügen: wähle oben die "+" Palette und füge Block ein — neue Blöcke
werden der zuletzt aktiven Spalte zugewiesen. (In M4 kommt pro-Spalte-Insert-UI.)
</p>
</div>
<style>
.wb-inspector {
display: flex;
flex-direction: column;
gap: 1rem;
}
.wb-field,
.wb-checkbox {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.wb-checkbox {
flex-direction: row;
align-items: center;
gap: 0.5rem;
font-size: 0.8125rem;
}
.wb-field > span {
font-size: 0.75rem;
font-weight: 500;
opacity: 0.7;
}
.wb-field select {
width: 100%;
padding: 0.5rem 0.625rem;
border-radius: 0.5rem;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.04);
color: inherit;
font-size: 0.875rem;
}
.wb-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
}
.wb-hint {
margin: 0;
padding: 0.5rem 0.75rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 0.375rem;
font-size: 0.75rem;
opacity: 0.6;
line-height: 1.4;
}
</style>