Compare commits
4 commits
7fed715d6b
...
46b0df992c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46b0df992c | ||
|
|
ac1eafca64 | ||
|
|
4766098296 | ||
|
|
c0fe32f9f5 |
14 changed files with 27267 additions and 1 deletions
|
|
@ -17,6 +17,7 @@ import { HorizontalRuleBlock } from '@/collections/blocks/HorizontalRule'
|
|||
import { BlogSliderBlock } from '@/collections/blocks/BlogSlider'
|
||||
import { MassTimesBlock } from '@/collections/blocks/MassTimes'
|
||||
import { CollapsibleImageWithTextBlock } from '@/collections/blocks/CollapsibleImageWithText'
|
||||
import { CollapsibleMapWithTextBlock } from '@/collections/blocks/CollapsibleMapWithText'
|
||||
import { CollapsiblesBlock } from '@/collections/blocks/Collapsibles'
|
||||
import { EventsBlock } from '@/collections/blocks/Events'
|
||||
import { PublicationAndNewsletterBlock } from '@/collections/blocks/PublicationAndNewsletter'
|
||||
|
|
@ -101,6 +102,7 @@ export const Pages: CollectionConfig = {
|
|||
HorizontalRuleBlock,
|
||||
BlogSliderBlock,
|
||||
CollapsibleImageWithTextBlock,
|
||||
CollapsibleMapWithTextBlock,
|
||||
CollapsiblesBlock,
|
||||
MassTimesBlock,
|
||||
EventsBlock,
|
||||
|
|
|
|||
53
src/collections/blocks/CollapsibleMapWithText.ts
Normal file
53
src/collections/blocks/CollapsibleMapWithText.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { Block } from 'payload'
|
||||
|
||||
export const CollapsibleMapWithTextBlock: Block = {
|
||||
slug: 'collapsibleMapWithText',
|
||||
labels: {
|
||||
singular: {
|
||||
de: 'Karte mit Text',
|
||||
},
|
||||
plural: {
|
||||
de: 'Karte mit Text',
|
||||
},
|
||||
},
|
||||
dbName: 'collaps_map',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: {
|
||||
de: 'Titel',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'text',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
label: {
|
||||
de: 'Text',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
label: {
|
||||
de: 'Aufklappbarer Inhalt',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
label: {
|
||||
de: 'Hintergrundfarbe',
|
||||
},
|
||||
options: [
|
||||
{ label: 'Standard', value: 'default' },
|
||||
{ label: 'Soft', value: 'soft' },
|
||||
{ label: 'Off-White', value: 'off-white' },
|
||||
],
|
||||
defaultValue: 'default',
|
||||
},
|
||||
],
|
||||
}
|
||||
18
src/components/ChemnitzMap/ChemnitzMap.stories.tsx
Normal file
18
src/components/ChemnitzMap/ChemnitzMap.stories.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { ChemnitzMap } from './ChemnitzMap'
|
||||
|
||||
const meta: Meta<typeof ChemnitzMap> = {
|
||||
component: ChemnitzMap,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ width: '100%', maxWidth: 1100, margin: '0 auto' }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof ChemnitzMap>
|
||||
export default meta
|
||||
|
||||
export const Default: Story = {}
|
||||
730
src/components/ChemnitzMap/ChemnitzMap.tsx
Normal file
730
src/components/ChemnitzMap/ChemnitzMap.tsx
Normal file
File diff suppressed because one or more lines are too long
930
src/components/ChemnitzMap/chemnitz_map.svg
Normal file
930
src/components/ChemnitzMap/chemnitz_map.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 145 KiB |
30
src/components/ChemnitzMap/styles.module.scss
Normal file
30
src/components/ChemnitzMap/styles.module.scss
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
.map {
|
||||
width: 100%;
|
||||
|
||||
:global(svg) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.parish {
|
||||
cursor: pointer;
|
||||
transform-box: fill-box;
|
||||
transform-origin: center;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
:global(path.cls-1) {
|
||||
transition: fill 0.3s ease;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
transform: scale(1.15);
|
||||
outline: none;
|
||||
|
||||
:global(path.cls-1) {
|
||||
fill: #0f6898;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import { Banner } from '@/components/Banner/Banner'
|
|||
import { MainText } from '@/components/MainText/MainText'
|
||||
import { HR } from '@/components/HorizontalRule/HorizontalRule'
|
||||
import { CollapsibleImageWithText } from '@/compositions/CollapsibleImageWithText/CollapsibleImageWithText'
|
||||
import { CollapsibleMapWithText } from '@/compositions/CollapsibleMapWithText/CollapsibleMapWithText'
|
||||
import { Collapsible } from '@/components/Collapsible/Collapsible'
|
||||
import { PublicationAndNewsletter } from '@/compositions/PublicationAndNewsletter/PublicationAndNewsletter'
|
||||
import { ContactPersonCard } from '@/components/ContactPersonCard/ContactPersonCard'
|
||||
|
|
@ -228,6 +229,25 @@ export function Blocks({ content }: BlocksProps) {
|
|||
)
|
||||
}
|
||||
|
||||
if (item.blockType === 'collapsibleMapWithText') {
|
||||
const bg = item.backgroundColor === 'soft' || item.backgroundColor === 'off-white'
|
||||
? item.backgroundColor as 'soft' | 'off-white'
|
||||
: undefined
|
||||
return (
|
||||
<CollapsibleMapWithText
|
||||
key={item.id}
|
||||
title={item.title}
|
||||
text={item.text}
|
||||
backgroundColor={bg}
|
||||
content={
|
||||
item.content
|
||||
? <HTMLText width={'3/4'} data={item.content} />
|
||||
: <></>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (item.blockType === 'collapsibles') {
|
||||
return (
|
||||
<Section key={item.id} padding={'small'}>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
import { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { CollapsibleMapWithText } from './CollapsibleMapWithText'
|
||||
import { Title } from '@/components/Title/Title'
|
||||
import { P } from '@/components/Text/Paragraph'
|
||||
|
||||
const meta: Meta<typeof CollapsibleMapWithText> = {
|
||||
component: CollapsibleMapWithText,
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof CollapsibleMapWithText>
|
||||
export default meta
|
||||
|
||||
export const OurParishes: Story = {
|
||||
args: {
|
||||
backgroundColor: 'soft',
|
||||
title: 'Unsere Gemeinden',
|
||||
text:
|
||||
'Unsere Pfarrei umfasst sieben Kirchen in und um Chemnitz. Klicken Sie auf eine Kirche, um mehr über die jeweilige Gemeinde zu erfahren – von Gottesdienstzeiten über Gruppen und Kreise bis hin zu Kontaktmöglichkeiten.\n' +
|
||||
'\n' +
|
||||
'Jede Gemeinde hat ihren eigenen Charakter und ihre eigene Geschichte, gemeinsam bilden sie ein lebendiges katholisches Netzwerk in der Region.',
|
||||
content: (
|
||||
<>
|
||||
<Title title={'Über unsere Pfarrei'} size={'md'} />
|
||||
<P width={'3/4'}>
|
||||
Die Pfarrei wurde gegründet, um die pastorale Arbeit der umliegenden Kirchgemeinden
|
||||
zu bündeln und gleichzeitig den lokalen Charakter jeder einzelnen Gemeinde zu
|
||||
bewahren. Auf der Karte sehen Sie die geografische Verteilung – von St. Marien im
|
||||
Stadtzentrum bis St. Antonius in Frankenberg.
|
||||
</P>
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export const NoExtraContent: Story = {
|
||||
args: {
|
||||
backgroundColor: 'soft',
|
||||
title: 'Unsere Gemeinden',
|
||||
text:
|
||||
'Unsere Pfarrei umfasst sieben Kirchen in und um Chemnitz. Klicken Sie auf eine Kirche, um mehr über die jeweilige Gemeinde zu erfahren – von Gottesdienstzeiten über Gruppen und Kreise bis hin zu Kontaktmöglichkeiten.\n' +
|
||||
'\n' +
|
||||
'Jede Gemeinde hat ihren eigenen Charakter und ihre eigene Geschichte, gemeinsam bilden sie ein lebendiges katholisches Netzwerk in der Region.',
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
'use client'
|
||||
|
||||
import { BackgroundColor, Section } from '@/components/Section/Section'
|
||||
import { ChemnitzMap } from '@/components/ChemnitzMap/ChemnitzMap'
|
||||
import { Title } from '@/components/Title/Title'
|
||||
import styles from './styles.module.scss'
|
||||
import { Container } from '@/components/Container/Container'
|
||||
import { P } from '@/components/Text/Paragraph'
|
||||
import { CollapsibleArrow } from '@/components/CollapsibleArrow/CollapsibleArrow'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
type CollapsibleMapWithTextProps = {
|
||||
backgroundColor?: BackgroundColor
|
||||
title: string
|
||||
text: string
|
||||
content?: React.ReactNode
|
||||
}
|
||||
|
||||
type MoreInformationProps = {
|
||||
isCollapsed: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const MoreInformation = ({ isCollapsed, onClick }: MoreInformationProps) => {
|
||||
const [direction, setDirection] = useState<'UP' | 'DOWN'>(isCollapsed ? 'DOWN' : 'UP')
|
||||
|
||||
const toggleDirection = () => {
|
||||
setDirection(direction == 'UP' ? 'DOWN' : 'UP')
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
toggleDirection()
|
||||
onClick()
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className={styles.more}
|
||||
onMouseEnter={toggleDirection}
|
||||
onMouseLeave={toggleDirection}
|
||||
>
|
||||
Mehr erfahren <CollapsibleArrow direction={direction} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export function CollapsibleMapWithText({
|
||||
title,
|
||||
text,
|
||||
content,
|
||||
backgroundColor,
|
||||
}: CollapsibleMapWithTextProps) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const ref2 = useRef<HTMLDivElement>(null)
|
||||
const [contentHeight, setContentHeight] = useState(0)
|
||||
const [isCollapsed, setIsCollapsed] = useState(true)
|
||||
|
||||
const collapse = () => {
|
||||
setIsCollapsed(true)
|
||||
ref.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
|
||||
const toggle = () => {
|
||||
if (isCollapsed) {
|
||||
setIsCollapsed(false)
|
||||
setTimeout(() => {
|
||||
ref2.current?.scrollIntoView({ behavior: 'smooth'})
|
||||
}, 500)
|
||||
|
||||
} else {
|
||||
collapse()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (ref2.current) {
|
||||
setContentHeight(ref2.current.scrollHeight)
|
||||
}
|
||||
}, [ref2])
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<Section backgroundColor={backgroundColor}>
|
||||
<Container>
|
||||
<Title title={title} size={'lg'} color={'base'} />
|
||||
</Container>
|
||||
|
||||
<div className={styles.mapContainer}>
|
||||
<ChemnitzMap/>
|
||||
</div>
|
||||
|
||||
<Container>
|
||||
<P width={'3/4'}>{text}</P>
|
||||
{content && <MoreInformation isCollapsed={isCollapsed} onClick={toggle} />}
|
||||
</Container>
|
||||
|
||||
</Section>
|
||||
|
||||
{content && (
|
||||
<div
|
||||
ref={ref2}
|
||||
className={classNames({ [styles.content]: true })}
|
||||
style={{ maxHeight: isCollapsed ? undefined : contentHeight }}
|
||||
>
|
||||
<Section backgroundColor={backgroundColor} padding={'small'} paddingBottom={'large'}>
|
||||
<Container>{content}</Container>
|
||||
|
||||
<div className={styles.endButton}>
|
||||
<CollapsibleArrow direction={'UP'} onClick={collapse} />
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
49
src/compositions/CollapsibleMapWithText/styles.module.scss
Normal file
49
src/compositions/CollapsibleMapWithText/styles.module.scss
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
@import "template.scss";
|
||||
|
||||
$maxWidth: 1100px;
|
||||
|
||||
.mapContainer {
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
max-width: $maxWidth;
|
||||
margin: 0 auto 40px auto;
|
||||
}
|
||||
|
||||
.mapContainer:hover {
|
||||
background-color: #fbfdff;
|
||||
}
|
||||
|
||||
.more {
|
||||
font-size: 18px;
|
||||
color: $base-color;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
display: inline-flex;
|
||||
gap: 10px;
|
||||
border: 0;
|
||||
background-color: inherit;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
transition: max-height 500ms ease-in;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.endButton {
|
||||
position: relative;
|
||||
top: 75px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.endButton svg {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 576px) {
|
||||
.endButton {
|
||||
top: 20px;
|
||||
}
|
||||
}
|
||||
25177
src/migrations/20260429_121105_collapsible_map_with_text.json
Normal file
25177
src/migrations/20260429_121105_collapsible_map_with_text.json
Normal file
File diff suppressed because it is too large
Load diff
56
src/migrations/20260429_121105_collapsible_map_with_text.ts
Normal file
56
src/migrations/20260429_121105_collapsible_map_with_text.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
CREATE TYPE "public"."enum_collaps_map_background_color" AS ENUM('default', 'soft', 'off-white');
|
||||
CREATE TYPE "public"."enum__collaps_map_v_background_color" AS ENUM('default', 'soft', 'off-white');
|
||||
CREATE TABLE "collaps_map" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" uuid NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"title" varchar,
|
||||
"text" varchar,
|
||||
"content" jsonb,
|
||||
"background_color" "enum_collaps_map_background_color" DEFAULT 'default',
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE "_collaps_map_v" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" uuid NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"title" varchar,
|
||||
"text" varchar,
|
||||
"content" jsonb,
|
||||
"background_color" "enum__collaps_map_v_background_color" DEFAULT 'default',
|
||||
"_uuid" varchar,
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
ALTER TABLE "announcement" ALTER COLUMN "date" SET DEFAULT '2026-05-03T12:11:04.492Z';
|
||||
ALTER TABLE "calendar" ALTER COLUMN "date" SET DEFAULT '2026-05-03T12:11:04.791Z';
|
||||
ALTER TABLE "classifieds" ALTER COLUMN "until" SET DEFAULT '2026-05-29T12:11:04.854Z';
|
||||
ALTER TABLE "collaps_map" ADD CONSTRAINT "collaps_map_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "_collaps_map_v" ADD CONSTRAINT "_collaps_map_v_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v"("id") ON DELETE cascade ON UPDATE no action;
|
||||
CREATE INDEX "collaps_map_order_idx" ON "collaps_map" USING btree ("_order");
|
||||
CREATE INDEX "collaps_map_parent_id_idx" ON "collaps_map" USING btree ("_parent_id");
|
||||
CREATE INDEX "collaps_map_path_idx" ON "collaps_map" USING btree ("_path");
|
||||
CREATE INDEX "_collaps_map_v_order_idx" ON "_collaps_map_v" USING btree ("_order");
|
||||
CREATE INDEX "_collaps_map_v_parent_id_idx" ON "_collaps_map_v" USING btree ("_parent_id");
|
||||
CREATE INDEX "_collaps_map_v_path_idx" ON "_collaps_map_v" USING btree ("_path");`)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "collaps_map" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "_collaps_map_v" DISABLE ROW LEVEL SECURITY;
|
||||
DROP TABLE "collaps_map" CASCADE;
|
||||
DROP TABLE "_collaps_map_v" CASCADE;
|
||||
ALTER TABLE "announcement" ALTER COLUMN "date" SET DEFAULT '2026-04-26T13:12:25.662Z';
|
||||
ALTER TABLE "calendar" ALTER COLUMN "date" SET DEFAULT '2026-04-26T13:12:25.946Z';
|
||||
ALTER TABLE "classifieds" ALTER COLUMN "until" SET DEFAULT '2026-05-23T13:12:26.003Z';
|
||||
DROP TYPE "public"."enum_collaps_map_background_color";
|
||||
DROP TYPE "public"."enum__collaps_map_v_background_color";`)
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ import * as migration_20260417_111855_event_occurrences from './20260417_111855_
|
|||
import * as migration_20260417_114727_simplify_recurring_events from './20260417_114727_simplify_recurring_events';
|
||||
import * as migration_20260423_115311 from './20260423_115311';
|
||||
import * as migration_20260423_131226_add_event_end_date_time from './20260423_131226_add_event_end_date_time';
|
||||
import * as migration_20260429_121105_collapsible_map_with_text from './20260429_121105_collapsible_map_with_text';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
|
|
@ -262,6 +263,11 @@ export const migrations = [
|
|||
{
|
||||
up: migration_20260423_131226_add_event_end_date_time.up,
|
||||
down: migration_20260423_131226_add_event_end_date_time.down,
|
||||
name: '20260423_131226_add_event_end_date_time'
|
||||
name: '20260423_131226_add_event_end_date_time',
|
||||
},
|
||||
{
|
||||
up: migration_20260429_121105_collapsible_map_with_text.up,
|
||||
down: migration_20260429_121105_collapsible_map_with_text.down,
|
||||
name: '20260429_121105_collapsible_map_with_text'
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -597,6 +597,29 @@ export interface Page {
|
|||
blockName?: string | null;
|
||||
blockType: 'collapsibleImageWithText';
|
||||
}
|
||||
| {
|
||||
title: string;
|
||||
text: string;
|
||||
content: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
backgroundColor?: ('default' | 'soft' | 'off-white') | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'collapsibleMapWithText';
|
||||
}
|
||||
| {
|
||||
items: {
|
||||
title: string;
|
||||
|
|
@ -2169,6 +2192,16 @@ export interface PagesSelect<T extends boolean = true> {
|
|||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
collapsibleMapWithText?:
|
||||
| T
|
||||
| {
|
||||
title?: T;
|
||||
text?: T;
|
||||
content?: T;
|
||||
backgroundColor?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
collapsibles?:
|
||||
| T
|
||||
| {
|
||||
|
|
|
|||
Loading…
Reference in a new issue