Files
opencode-skill/skills/website-creator/payload-lexical-integration/SKILL.md
Kunthawat Greethong b26c8199a5 Update skills: add website-creator, mql-developer, ecommerce-astro
Changes:
- Add FAL_KEY and GEMINI_API_KEY to .env.example
- Update picture-it to use ~/.config/opencode/.env (unified creds)
- Remove shodh-memory skill (no longer used)
- Remove alphaear-* skills (deprecated)
- Remove thai-frontend-dev skill (replaced by website-creator)
- Remove theme-factory skill
- Add mql-developer skill (MQL5 trading)
- Add ecommerce-astro skill (Astro e-commerce)
- Add website-creator skill (Next.js + Payload CMS)
- Update install script for new skills
2026-04-16 17:40:27 +07:00

9.5 KiB

name, description, category
name description category
payload-lexical-integration แนวทางการรวม Payload CMS Lexical richText content กับ design system components — อธิบายว่าทำไม design skill output กับ Payload content ถึงอยู่คนละ layer และวิธี integrate มันเข้าด้วยกัน software-development

Payload Lexical Integration

ปัญหา

เวลาใช้ design skill (ui-ux-pro-max) กับ Payload CMS มักเกิดความสับสน:

  • Design skill ให้โค้ดแบบไหน?
  • Payload Lexical เก็บ content ยังไง?
  • ทำไม content ไม่แสดงหลังสร้าง fields เสร็จ?

สิ่งที่ต้องเข้าใจก่อน

Two Layers — แยกกันทำ

┌─────────────────────────────────────────────────────────┐
│  DESIGN LAYER (ui-ux-pro-max, ckm:design, ckm:ui-styling)│
│  • Component structure (Hero, Card, Navbar)              │
│  • Color tokens, typography, spacing                    │
│  • Animation specs (150-300ms, ease-out)                 │
│  • Layout grid, responsive breakpoints                   │
│  • Interaction states                                    │
│                                                          │
│  Output: React + Tailwind code — "ภาชนะ" ไม่ใช่ "เนื้อหา"│
└─────────────────────────────────────────────────────────┘
                           ↓
┌─────────────────────────────────────────────────────────┐
│  CONTENT LAYER (Payload CMS)                            │
│  • ข้อความ + format (bold, italic, link)               │
│  • Headings (H1-H6)                                     │
│  • Lists, blockquotes, code blocks                      │
│  • Images, links                                        │
│  • Tables                                               │
│                                                          │
│  Output: Lexical JSON — "เนื้อหา" ไม่ใช่ "ภาชนะ"       │
└─────────────────────────────────────────────────────────┘

Design skill สร้าง "ภาชนะ" — Payload สร้าง "เนื้อหา" — ต้องรวมกันตอน render


ขั้นตอน

[1] Design Phase
    ui-ux-pro-max → Component structure, tokens, animations
    Output: Component skeleton (ไม่มี content)
         ↓
[2] Payload Phase
    สร้าง Collections + richText Fields
    Output: Content structure ใน Payload
         ↓
[3] Content Phase
    พิมพ์ content ใน /admin (Lexical visual editor)
    Output: Lexical JSON
         ↓
[4] Integration Phase
    ครอบ Payload content ด้วย Design components

Step 1: Payload Collection

กำหนด content fields ตาม section:

// src/collections/Posts.ts
const Posts: CollectionConfig = {
  slug: 'posts',
  fields: [
    { name: 'title',          type: 'text',     required: true },
    { name: 'slug',            type: 'text',     required: true },
    { name: 'heroContent',    type: 'richText' },   // content สำหรับ Hero
    { name: 'features',       type: 'array',
      fields: [
        { name: 'heading', type: 'text' },
        { name: 'content', type: 'richText' },       // content ในแต่ละ card
      ]
    },
    { name: 'testimonial',    type: 'richText' },
    { name: 'featuredImage',  type: 'upload', relationTo: 'media' },
    { name: 'status',          type: 'select', options: [...], defaultValue: 'draft' },
  ],
}

Step 2: สร้าง Payload Helpers

// src/lib/payload-helpers.ts
import { getPayload } from 'payload'
import config from '@/payload.config'

export async function getPost(slug: string) {
  const p = await getPayload({ config })
  const { docs } = await p.find({
    collection: 'posts',
    where: { slug: { equals: slug } },
    depth: 2,
  })
  return docs[0] ?? null
}

export async function getAllPosts() {
  const p = await getPayload({ config })
  return p.find({
    collection: 'posts',
    where: { status: { equals: 'published' } },
    depth: 1,
  })
}

Step 3: Integration — Design Component + RichText

// src/app/(frontend)/posts/[slug]/page.tsx
import { getPost } from '@/lib/payload-helpers'
import { RichText } from '@payloadcms/richtext-lexical'

// Design tokens จาก ui-ux-pro-max
const tokens = {
  hero:    'text-5xl md:text-7xl font-bold tracking-tight',
  section: 'py-20 px-6 max-w-7xl mx-auto',
  card:   'rounded-2xl border border-slate-200 p-6 shadow-sm',
  animate: 'animate-fade-in duration-300 ease-out',
}

// Design component ครอบ Payload richText
function HeroSection({ title, content }: { title: string; content: any }) {
  return (
    <section className={`${tokens.section} text-center`}>
      <h1 className={`${tokens.hero} mb-6`}>{title}</h1>
      {content && (
        <div className="max-w-3xl mx-auto">
          {/* Payload content → RichText → design wrapper */}
          <RichText data={content} className="prose prose-lg" />
        </div>
      )}
    </section>
  )
}

function FeatureCard({ heading, content }: { heading: string; content: any }) {
  return (
    <div className={`${tokens.card} ${tokens.animate}`}>
      <h3 className="text-xl font-semibold mb-3">{heading}</h3>
      {content && <RichText data={content} className="prose prose-sm" />}
    </div>
  )
}

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
  if (!post) return <div>Not found</div>

  return (
    <main className="min-h-screen">
      <HeroSection title={post.title} content={post.heroContent} />

      {post.features?.length > 0 && (
        <section className={`${tokens.section} bg-slate-50`}>
          <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
            {post.features.map((f: any, i: number) => (
              <FeatureCard key={i} heading={f.heading} content={f.content} />
            ))}
          </div>
        </section>
      )}
    </main>
  )
}

Animation

Animation apply ที่ wrapper element ไม่ใช่ที่ content — เพราะ Lexical JSON เก็บแค่ content structure ไม่เก็บ animation metadata

// ✅ ถูก — animation ที่ wrapper
<div className="animate-hero-in">
  <RichText data={post.content} />
</div>

// ❌ ผิด — พยายามใส่ animation ใน Lexical JSON

Design skill จะให้ animation spec เป็น CSS class — แค่ apply ที่ element ที่ wrap <RichText>


Tailwind Typography Setup

pnpm add @tailwindcss/typography
// tailwind.config.ts
plugins: [require('@tailwindcss/typography')],

ใช้ class prose กับ <RichText>:

<RichText data={post.content} className="prose prose-lg max-w-none" />

Payload Config: เปิด Lexical Editor

// payload.config.ts
import { lexicalEditor } from '@payloadcms/richtext-lexical'

export default buildConfig({
  editor: lexicalEditor(),   // ← ต้องมีถึงจะใช้ visual editor ได้
  // ...
})

Common Mistakes

1. Design skill ให้ hardcode content

Design skill อาจให้แบบนี้:

// ❌ สิ่งที่ design skill อาจให้มา
<div className="hero">
  <h1>Welcome to Our Site</h1>   // hardcode
  <p>Amazing content here...</p>  // hardcode
</div>

ต้องแปลงเป็น:

// ✅
<div className="hero animate-hero-in">
  <h1>{post.title}</h1>
  {post.heroContent && (
    <RichText data={post.heroContent} className="prose" />
  )}
</div>

2. ลืม lexicalEditor() ใน payload.config

ถ้าไม่มี editor: lexicalEditor() → visual editor จะไม่ขึ้น

3. ลืม Tailwind typography plugin

ถ้าไม่มี @tailwindcss/typography → richText output จะไม่มี styling


สรุป: ใครทำอะไร

Design Layer ทำ Payload Layer ทำ Integration ทำ
Component structure Content storage ครอบ RichText ด้วย design component
Color/tokens richText fields Apply design tokens กับ Payload output
Typography system Visual editor (/admin) Style richText output ด้วย prose class
Animation specs Content rendering Wrap output ด้วย animation classes
Layout grid SEO fields (via plugin) Layout คงที่ + content จาก Payload

  • website-creator — workflow หลักในการสร้างเว็บด้วย Next.js + Payload
  • payload — Payload CMS skill (fields, hooks, queries, plugins)