From 5775ab3a91466370387b1840fa08d2ca316f4255 Mon Sep 17 00:00:00 2001 From: Mackie Date: Wed, 3 Jun 2026 11:50:30 +0800 Subject: [PATCH] kanban --- src/blocks/KanbanColorBlock/Component.tsx | 54 +++++++++++++++++++++++ src/blocks/KanbanColorBlock/config.ts | 47 ++++++++++++++++++++ src/blocks/KanbanHoriBlock/Component.tsx | 36 +++++++++++++++ src/blocks/KanbanHoriBlock/config.ts | 32 ++++++++++++++ src/blocks/RenderBlocks.tsx | 4 ++ src/collections/Pages/index.ts | 16 ++++++- 6 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 src/blocks/KanbanColorBlock/Component.tsx create mode 100644 src/blocks/KanbanColorBlock/config.ts create mode 100644 src/blocks/KanbanHoriBlock/Component.tsx create mode 100644 src/blocks/KanbanHoriBlock/config.ts diff --git a/src/blocks/KanbanColorBlock/Component.tsx b/src/blocks/KanbanColorBlock/Component.tsx new file mode 100644 index 0000000..24de526 --- /dev/null +++ b/src/blocks/KanbanColorBlock/Component.tsx @@ -0,0 +1,54 @@ +// src/blocks/KanbanColor/Component.tsx +import React from 'react' + +const colorMap: Record = { + gray: { border: 'border-t-[#888780]', dot: 'bg-[#888780]' }, + blue: { border: 'border-t-[#378ADD]', dot: 'bg-[#378ADD]' }, + green: { border: 'border-t-[#1D9E75]', dot: 'bg-[#1D9E75]' }, + amber: { border: 'border-t-[#BA7517]', dot: 'bg-[#BA7517]' }, + red: { border: 'border-t-[#E24B4A]', dot: 'bg-[#E24B4A]' }, + purple: { border: 'border-t-[#7F77DD]', dot: 'bg-[#7F77DD]' }, + teal: { border: 'border-t-[#1D9E75]', dot: 'bg-[#1D9E75]' }, +} + +type Card = { title: string; subtitle?: string } +type Column = { title: string; color?: string; cards?: Card[] } +type Props = { columns?: Column[] } + +export function KanbanColorBlock({ columns }: Props) { + if (!Array.isArray(columns) || columns.length === 0) return null + + return ( +
+
+ {columns.map((col, i) => { + const { border, dot } = colorMap[col.color ?? 'gray'] ?? colorMap.gray + return ( +
+
+ + {col.title} + + {col.cards?.length ?? 0} + +
+ {Array.isArray(col.cards) && + col.cards.map((card, j) => ( +
+

{card.title}

+ {card.subtitle &&

{card.subtitle}

} +
+ ))} +
+ ) + })} +
+
+ ) +} diff --git a/src/blocks/KanbanColorBlock/config.ts b/src/blocks/KanbanColorBlock/config.ts new file mode 100644 index 0000000..a67a045 --- /dev/null +++ b/src/blocks/KanbanColorBlock/config.ts @@ -0,0 +1,47 @@ +// src/blocks/KanbanColor/config.ts +import type { Block } from 'payload' + +export const KanbanColorBlock: Block = { + slug: 'kanbanColor', + labels: { singular: 'Kanban (Color)', plural: 'Kanbans (Color)' }, + fields: [ + { + name: 'columns', + type: 'array', + label: 'Columns', + minRows: 1, + fields: [ + { + name: 'title', + type: 'text', + label: 'Column title', + required: true, + }, + { + name: 'color', + type: 'select', + label: 'Accent color', + defaultValue: 'gray', + options: [ + { label: 'Gray', value: 'gray' }, + { label: 'Blue', value: 'blue' }, + { label: 'Green', value: 'green' }, + { label: 'Amber', value: 'amber' }, + { label: 'Red', value: 'red' }, + { label: 'Purple', value: 'purple' }, + { label: 'Teal', value: 'teal' }, + ], + }, + { + name: 'cards', + type: 'array', + label: 'Cards', + fields: [ + { name: 'title', type: 'text', label: 'Title', required: true }, + { name: 'subtitle', type: 'text', label: 'Subtitle / Category' }, + ], + }, + ], + }, + ], +} diff --git a/src/blocks/KanbanHoriBlock/Component.tsx b/src/blocks/KanbanHoriBlock/Component.tsx new file mode 100644 index 0000000..809c4d3 --- /dev/null +++ b/src/blocks/KanbanHoriBlock/Component.tsx @@ -0,0 +1,36 @@ +// src/blocks/KanbanHori/Component.tsx +import React from 'react' + +type Card = { title: string; subtitle?: string } +type Row = { label: string; cards?: Card[] } +type Props = { rows?: Row[] } + +export function KanbanHoriBlock({ rows }: Props) { + if (!Array.isArray(rows) || rows.length === 0) return null + + return ( +
+
+ {rows.map((row, i) => ( +
+
+ {row.label} +
+
+ {Array.isArray(row.cards) && + row.cards.map((card, j) => ( +
+

{card.title}

+ {card.subtitle &&

{card.subtitle}

} +
+ ))} +
+
+ ))} +
+
+ ) +} diff --git a/src/blocks/KanbanHoriBlock/config.ts b/src/blocks/KanbanHoriBlock/config.ts new file mode 100644 index 0000000..583ec0f --- /dev/null +++ b/src/blocks/KanbanHoriBlock/config.ts @@ -0,0 +1,32 @@ +// src/blocks/KanbanHori/config.ts +import type { Block } from 'payload' + +export const KanbanHoriBlock: Block = { + slug: 'kanbanHori', + labels: { singular: 'Kanban (Horizontal)', plural: 'Kanbans (Horizontal)' }, + fields: [ + { + name: 'rows', + type: 'array', + label: 'Rows', + minRows: 1, + fields: [ + { + name: 'label', + type: 'text', + label: 'Row label', + required: true, + }, + { + name: 'cards', + type: 'array', + label: 'Cards', + fields: [ + { name: 'title', type: 'text', label: 'Title', required: true }, + { name: 'subtitle', type: 'text', label: 'Subtitle / Category' }, + ], + }, + ], + }, + ], +} diff --git a/src/blocks/RenderBlocks.tsx b/src/blocks/RenderBlocks.tsx index 1092345..1618440 100644 --- a/src/blocks/RenderBlocks.tsx +++ b/src/blocks/RenderBlocks.tsx @@ -9,6 +9,8 @@ import { FormBlock } from '@/blocks/Form/Component' import { MediaBlock } from '@/blocks/MediaBlock/Component' import { SkillsBlock } from '@/blocks/Skills/Component' import { SkillsMarqueeBlock } from '@/blocks/SkillsMarquee/Component' +import { KanbanColorBlock } from '@/blocks/KanbanColor/Component' +import { KanbanHoriBlock } from '@/blocks/KanbanHori/Component' const blockComponents = { archive: ArchiveBlock, @@ -18,6 +20,8 @@ const blockComponents = { mediaBlock: MediaBlock, skills: SkillsBlock, skillsMarquee: SkillsMarqueeBlock, + kanbanColor: KanbanColorBlock, + kanbanHori: KanbanHoriBlock, } export const RenderBlocks: React.FC<{ diff --git a/src/collections/Pages/index.ts b/src/collections/Pages/index.ts index 9b1c0ab..ac84a53 100644 --- a/src/collections/Pages/index.ts +++ b/src/collections/Pages/index.ts @@ -9,6 +9,8 @@ import { FormBlock } from '../../blocks/Form/config' import { MediaBlock } from '../../blocks/MediaBlock/config' import { SkillsBlock } from '../../blocks/Skills/config' import { SkillsMarqueeBlock } from '../../blocks/SkillsMarquee/config' +import { KanbanColorBlock } from '../../blocks/KanbanColor/config' +import { KanbanHoriBlock } from '../../blocks/KanbanHori/config' import { hero } from '@/heros/config' import { slugField } from 'payload' import { populatePublishedAt } from '../../hooks/populatePublishedAt' @@ -71,7 +73,17 @@ export const Pages: CollectionConfig<'pages'> = { { name: 'layout', type: 'blocks', - blocks: [CallToAction, Content, MediaBlock, Archive, FormBlock, SkillsBlock, SkillsMarqueeBlock], + blocks: [ + CallToAction, + Content, + MediaBlock, + Archive, + FormBlock, + SkillsBlock, + SkillsMarqueeBlock, + KanbanColorBlock, + KanbanHoriBlock, + ], required: true, admin: { initCollapsed: true, @@ -128,4 +140,4 @@ export const Pages: CollectionConfig<'pages'> = { }, maxPerDoc: 50, }, -} \ No newline at end of file +}