kanban
This commit is contained in:
parent
0b2e32bd24
commit
5775ab3a91
6 changed files with 187 additions and 2 deletions
54
src/blocks/KanbanColorBlock/Component.tsx
Normal file
54
src/blocks/KanbanColorBlock/Component.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
// src/blocks/KanbanColor/Component.tsx
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const colorMap: Record<string, { border: string; dot: string }> = {
|
||||||
|
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 (
|
||||||
|
<section className="w-full max-w-5xl mx-auto px-6 py-16">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{columns.map((col, i) => {
|
||||||
|
const { border, dot } = colorMap[col.color ?? 'gray'] ?? colorMap.gray
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`bg-muted/50 rounded-xl p-4 flex flex-col gap-3 border-t-[3px] ${border}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<span className={`w-2 h-2 rounded-full shrink-0 ${dot}`} />
|
||||||
|
<span className="text-sm font-medium text-foreground/80">{col.title}</span>
|
||||||
|
<span className="ml-auto text-xs text-foreground/30 border border-foreground/10 rounded-full px-2 py-0.5">
|
||||||
|
{col.cards?.length ?? 0}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{Array.isArray(col.cards) &&
|
||||||
|
col.cards.map((card, j) => (
|
||||||
|
<div
|
||||||
|
key={j}
|
||||||
|
className="bg-background/70 border border-foreground/8 rounded-lg px-3 py-2.5 flex flex-col gap-0.5"
|
||||||
|
>
|
||||||
|
<p className="text-sm font-medium text-foreground/85">{card.title}</p>
|
||||||
|
{card.subtitle && <p className="text-xs text-foreground/40">{card.subtitle}</p>}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
47
src/blocks/KanbanColorBlock/config.ts
Normal file
47
src/blocks/KanbanColorBlock/config.ts
Normal file
|
|
@ -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' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
36
src/blocks/KanbanHoriBlock/Component.tsx
Normal file
36
src/blocks/KanbanHoriBlock/Component.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<section className="w-full max-w-5xl mx-auto px-6 py-16">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
{rows.map((row, i) => (
|
||||||
|
<div key={i} className="grid grid-cols-[100px_1fr] gap-4 items-start">
|
||||||
|
<div className="text-xs font-medium text-foreground/50 text-right pr-4 pt-2.5 border-r border-foreground/10">
|
||||||
|
{row.label}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{Array.isArray(row.cards) &&
|
||||||
|
row.cards.map((card, j) => (
|
||||||
|
<div
|
||||||
|
key={j}
|
||||||
|
className="bg-muted/50 border border-foreground/8 rounded-lg px-3 py-2.5 flex flex-col gap-0.5 min-w-[120px]"
|
||||||
|
>
|
||||||
|
<p className="text-sm font-medium text-foreground/85">{card.title}</p>
|
||||||
|
{card.subtitle && <p className="text-xs text-foreground/40">{card.subtitle}</p>}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
32
src/blocks/KanbanHoriBlock/config.ts
Normal file
32
src/blocks/KanbanHoriBlock/config.ts
Normal file
|
|
@ -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' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,8 @@ import { FormBlock } from '@/blocks/Form/Component'
|
||||||
import { MediaBlock } from '@/blocks/MediaBlock/Component'
|
import { MediaBlock } from '@/blocks/MediaBlock/Component'
|
||||||
import { SkillsBlock } from '@/blocks/Skills/Component'
|
import { SkillsBlock } from '@/blocks/Skills/Component'
|
||||||
import { SkillsMarqueeBlock } from '@/blocks/SkillsMarquee/Component'
|
import { SkillsMarqueeBlock } from '@/blocks/SkillsMarquee/Component'
|
||||||
|
import { KanbanColorBlock } from '@/blocks/KanbanColor/Component'
|
||||||
|
import { KanbanHoriBlock } from '@/blocks/KanbanHori/Component'
|
||||||
|
|
||||||
const blockComponents = {
|
const blockComponents = {
|
||||||
archive: ArchiveBlock,
|
archive: ArchiveBlock,
|
||||||
|
|
@ -18,6 +20,8 @@ const blockComponents = {
|
||||||
mediaBlock: MediaBlock,
|
mediaBlock: MediaBlock,
|
||||||
skills: SkillsBlock,
|
skills: SkillsBlock,
|
||||||
skillsMarquee: SkillsMarqueeBlock,
|
skillsMarquee: SkillsMarqueeBlock,
|
||||||
|
kanbanColor: KanbanColorBlock,
|
||||||
|
kanbanHori: KanbanHoriBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RenderBlocks: React.FC<{
|
export const RenderBlocks: React.FC<{
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import { FormBlock } from '../../blocks/Form/config'
|
||||||
import { MediaBlock } from '../../blocks/MediaBlock/config'
|
import { MediaBlock } from '../../blocks/MediaBlock/config'
|
||||||
import { SkillsBlock } from '../../blocks/Skills/config'
|
import { SkillsBlock } from '../../blocks/Skills/config'
|
||||||
import { SkillsMarqueeBlock } from '../../blocks/SkillsMarquee/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 { hero } from '@/heros/config'
|
||||||
import { slugField } from 'payload'
|
import { slugField } from 'payload'
|
||||||
import { populatePublishedAt } from '../../hooks/populatePublishedAt'
|
import { populatePublishedAt } from '../../hooks/populatePublishedAt'
|
||||||
|
|
@ -71,7 +73,17 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||||
{
|
{
|
||||||
name: 'layout',
|
name: 'layout',
|
||||||
type: 'blocks',
|
type: 'blocks',
|
||||||
blocks: [CallToAction, Content, MediaBlock, Archive, FormBlock, SkillsBlock, SkillsMarqueeBlock],
|
blocks: [
|
||||||
|
CallToAction,
|
||||||
|
Content,
|
||||||
|
MediaBlock,
|
||||||
|
Archive,
|
||||||
|
FormBlock,
|
||||||
|
SkillsBlock,
|
||||||
|
SkillsMarqueeBlock,
|
||||||
|
KanbanColorBlock,
|
||||||
|
KanbanHoriBlock,
|
||||||
|
],
|
||||||
required: true,
|
required: true,
|
||||||
admin: {
|
admin: {
|
||||||
initCollapsed: true,
|
initCollapsed: true,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue