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

291 lines
9.5 KiB
Markdown

---
name: payload-lexical-integration
description: แนวทางการรวม Payload CMS Lexical richText content กับ design system components — อธิบายว่าทำไม design skill output กับ Payload content ถึงอยู่คนละ layer และวิธี integrate มันเข้าด้วยกัน
category: 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:
```ts
// 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
```ts
// 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
```tsx
// 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
```tsx
// ✅ ถูก — 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
```bash
pnpm add @tailwindcss/typography
```
```ts
// tailwind.config.ts
plugins: [require('@tailwindcss/typography')],
```
ใช้ class `prose` กับ `<RichText>`:
```tsx
<RichText data={post.content} className="prose prose-lg max-w-none" />
```
---
## Payload Config: เปิด Lexical Editor
```ts
// 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 อาจให้แบบนี้:
```tsx
// ❌ สิ่งที่ design skill อาจให้มา
<div className="hero">
<h1>Welcome to Our Site</h1> // hardcode
<p>Amazing content here...</p> // hardcode
</div>
```
ต้องแปลงเป็น:
```tsx
// ✅
<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 |
---
## Related
- `website-creator` — workflow หลักในการสร้างเว็บด้วย Next.js + Payload
- `payload` — Payload CMS skill (fields, hooks, queries, plugins)