قاب رسانه (MediaFrame)

یک تصویر، ویدئو یا صوت با قاعدهٔ واحد برای هر چیدمان؛ در کارت و جزئیات هرگز بریده نمی‌شود

معرفی

MediaFrame یک رسانهٔ پست را با قاعدهٔ همان چیدمانی که در آن نشسته نمایش می‌دهد. قاعده‌ها تصمیم محصول‌اند و در خود جزء ثابت شده‌اند، پس هیچ صفحه‌ای لازم نیست دوباره دربارهٔ برش یا نسبت تصویر تصمیم بگیرد:

چیدمانقاعده
cardتمام عرض کارت، با نسبت خود رسانه در بازهٔ 4:5 تا 1.91:1 و حداکثر 500 پیکسل ارتفاع. بیرون از بازه یا بلندتر: کل رسانه روی نسخهٔ محوشدهٔ خودش. هرگز بریده نمی‌شود.
tileتنها چیدمانی که برش دارد: مربع 1:1 یا عمودی 4:5. خود قاب تعاملی نیست؛ موردِ دور کاشی با کلیک جزئیات را باز می‌کند.
rowبندانگشتی مربع 96 پیکسلی (72 در حالت فشرده) کنار متن.
detailsبزرگ و کامل: نسبت خود رسانه تا 70٪ ارتفاع صفحه؛ ویدئو با پخش‌کننده.

کادر از روی width و height رسانه، پیش از بارگذاری، ساخته می‌شود؛ پس وقتی تصویر می‌رسد چیزی جابه‌جا نمی‌شود. رسانهٔ بدون ابعاد کادر مربع می‌گیرد و کامل در آن نمایش داده می‌شود؛ ردیف API جست‌وجوی محتوا ابعاد ندارد (بخش «رسانهٔ بدون ابعاد» را ببینید).

جزء پایه، نه کارت پست

این جزء یکی از اجزای پایهٔ @partodata/ui/social است که Post، Comment و Account روی آن‌ها ساخته شده‌اند. پست، نظر یا حساب را با همان کامپوننت‌ها نمایش دهید و فهرست آن‌ها را با EntityCollection؛ کارت یا ردیف را خودتان از اجزای پایه نسازید. جدول انتخاب در مدل محتوای اجتماعی است.

چه زمانی استفاده کنیم:

  • هر جا رسانهٔ یک پست، نظر یا حساب نمایش داده می‌شود: کارت خبرخوان، ردیف فهرست، کاشی شبکه، نمای جزئیات.
  • وقتی رسانه دیر می‌رسد، منقضی می‌شود یا ذخیره نشده است؛ این وضعیت‌ها همان کادر را نگه می‌دارند.

چه زمانی استفاده نکنیم:

  • برای تصویر تزئینی، لوگو یا آواتار: از Avatar استفاده کنید؛ برای یک تصویر ساده با جایگزین هنگام خطا، layout="fill" همین کامپوننت (از 5.0 جای SafeImage).
  • برای بریدن تصویر در کارت پایش: این کار عمداً ممکن نیست؛ اینفوگرافی‌های پرمتن نباید بریده شوند. اگر برش لازم است، چیدمان tile است.
card · 4:5
card · 16:9
card · 9:16
ویدئو، 0:08
card · video 9:16

استفاده

import { MediaFrame, type SocialMedia } from '@partodata/ui/social'

const media: SocialMedia = {
  kind: 'image',
  src: 'https://cdn.example.com/p/1.jpg',
  width: 1080,
  height: 1350,
}

export function PostMedia({ index }: { index: number }) {
  return <MediaFrame media={media} layout="card" priority={index < 2} />
}

priority برای چند مورد اول بالای صفحه است: بارگذاری فوری با اولویت بالا. بقیه تنبل بارگذاری می‌شوند.

پراکسی و امضای نشانی، یک بار برای کل برنامه

SocialMediaProvider را یک بار در چیدمان کلاینتی برنامه بگذارید. resolveSrc آن هر نشانی رسانه (تصویر، پوستر و ویدئو) را پیش از بارگذاری به نشانی نهایی تبدیل می‌کند: پراکسی تصویر، نشانی امضاشده یا تغییر اندازه در CDN. اگر undefined برگرداند، رسانه «در دسترس نیست» نمایش داده می‌شود. همهٔ قاب‌های داخل آن، در فهرست و در جزئیات، از همین قاعده استفاده می‌کنند؛ resolveSrc روی یک قاب فقط برای همان قاب جای آن را می‌گیرد.

'use client'

import type { ReactNode } from 'react'
import { SocialMediaProvider } from '@partodata/ui/social'

export function MediaProxy({ children }: { children: ReactNode }) {
  return (
    <SocialMediaProvider resolveSrc={(url) => `/api/media?u=${encodeURIComponent(url)}`}>
      {children}
    </SocialMediaProvider>
  )
}

حالت‌ها و انواع

چیدمان‌ها

tile · 16:9
tile · 1:1
tile · 4:5
tile · 5:4
tile · 9:16
ویدئو، 0:08
tile · video 16:9
ویدئو، 0:08
tile · video 9:16
ویدئو، 0:08
ویدئو، 0:08
details · 16:9

وضعیت‌ها

pending، failed، expired و unavailable (ذخیره نشده) همان کادر را با یک نشانه و برچسب نگه می‌دارند؛ نوار صوت در کارت و جزئیات کادر نسبت‌دار ندارد و وضعیتش یک ردیف در همان نوار است. نشانی‌ای که هنگام نمایش خطا بدهد هم failed می‌شود؛ اگر onRetry داده شده باشد دکمهٔ «تلاش دوباره» نمایش داده می‌شود (در ردیف، فقط نشانه؛ onRetry و count در row خطای نوع‌اند). رسانهٔ حساس در کارت و جزئیات تا وقتی کاربر بخواهد محو می‌ماند و در کاشی و ردیف محو باقی می‌ماند.

pending
failed
expired
مشاهده در منبع
unavailable (not stored)
sensitive
no size
صوت0:42
audio · ready
audio · pending
audio · failed
audio · expired

ذخیره‌شده، ذخیره‌نشده، بدون رسانه

سه حالت رسانهٔ یک پست را از هم جدا نشان دهید:

حالتدادهنمایش
رسانه را ذخیره کرده‌ایم{ kind, src, width, height }خود رسانه
در منبع رسانه دارد، ما ذخیره نکرده‌ایم{ kind, status: 'unavailable' }همان کادر محدود، نشانهٔ نوع (تصویر، ویدئو، چندرسانه‌ای با تعداد)، «رسانه ذخیره نشده است» و پیوند «مشاهده در منبع» با sourceUrl
در منبع رسانه ندارد (اغلب تلگرام و X)بدون mediaهیچ کادری؛ کارت متنی فشرده می‌ماند

unavailable یعنی «ذخیره نشده»؛ وضعیت جدیدی لازم نیست، چون مبدل‌ها همین معنا را از قبل به آن نگاشت می‌کنند و نشانی‌ای که resolveSrc رد کند هم یعنی نسخه‌ای از رسانه نزد ما نیست. failed و expired معنای خودشان را دارند: رسانه را داشتیم و بارگذاری نشد یا منقضی شد؛ نشانهٔ هشدار یا زمان دارند و هیچ‌کدام شبیه حالت ذخیره‌نشده نیست.

فروشگاه نمونه
@sample_shop
جشنوارهٔ تخفیف فصلی از امروز شروع شد و تا پایان هفته ادامه دارد. برای دیدن فهرست محصولات و شرایط ارسال به صفحهٔ فروشگاه سر بزنید. #تخفیف #پاییز
  • لایک12,400
  • نظر0
  • نرخ تعامل2.1٪
stored
کافه نمونه
@cafe_example2
بسته‌بندی تازهٔ محصول را در نمایشگاه دیدید؟ نظرتان را برای ما بنویسید؛ همهٔ پیام‌ها را می‌خوانیم و در طراحی نسخهٔ بعد به کار می‌بریم. @sample_shop
مشاهده در منبع
3 رسانه
  • لایک12,400
  • نظر86
  • نرخ تعاملنامعلوم
not stored
Brand Studio
@brand.studio
رونمایی محصول جدید با حضور مشتری‌ها برگزار شد. گزارش کامل و عکس‌های مراسم: https://example.com/launch#photos
  • بازدید34,000
no media
زمین بازی
مشاهده در منبع
تنظیمات
ظاهر
داده
1
حالت
import { MediaFrame, type SocialMedia } from '@partodata/ui/social'

const media: SocialMedia = {
  "kind": "image",
  "src": "https://ui.partodata.com/instagram-posts/images/image%204x5.png",
  "width": 896,
  "height": 1152,
  "status": "unavailable"
}

export function MediaFrameExample() {
  return (
    <MediaFrame
      media={media}
      sourceUrl="https://www.instagram.com/p/X1/"
    />
  )
}

رسانهٔ بدون ابعاد

API جست‌وجوی محتوا برای هر پست فقط thumbnailUrl می‌فرستد و عرض و ارتفاع ندارد، پس fromSearchContent به رسانه ابعادی نمی‌دهد و قاب کادر جایگزین را می‌گیرد: مربع. در card این یعنی قابی به عرض ستون و حداکثر 500 پیکسل ارتفاع که رسانه کامل در آن است؛ تصویر افقی 16:9 به‌جای پر کردن عرض، بالا و پایینش نوار محو می‌گیرد. در details کادر مربع است. row و tile کادر ثابت دارند و تفاوتی نمی‌کنند. هیچ‌وقت چیزی بریده نمی‌شود و صفحه هم بعد از بارگذاری جابه‌جا نمی‌شود.

برای کادر درست، ابعاد را در مبدل با گزینهٔ mediaSize بدهید: از پراکسی رسانه، از جدول ابعادی که بر اساس نشانی نگه می‌دارید، یا از خود API وقتی ابعاد تصویر بندانگشتی را بفرستد. روش کار در مدل محتوای اجتماعی است. در هر ردیف پیش‌نمایش، قاب اول (سمت راست) همان ردیف API بدون ابعاد است و قاب دوم همان ردیف با mediaSize:

16:9 · API row, no size
16:9 · with mediaSize
4:5 · API row, no size
4:5 · with mediaSize
ویدئو
video 16:9 · API row, no size
ویدئو
video 16:9 · with mediaSize

ویدئو و صوت

ویدئو در card، tile و row با پوستر، نشانهٔ پخش و مدت نمایش داده می‌شود و خود فایل بارگذاری نمی‌شود؛ در details پخش‌کنندهٔ واقعی دارد. ویدئوی بدون پوستر اولین قاب خودش را نشان می‌دهد، که یعنی بارگذاری خود فایل؛ برای همین فقط وقتی قاب به محدودهٔ دید نزدیک شود (یا با priority) بارگذاری می‌شود و تا آن زمان کادر خنثی با نشانهٔ پخش است. هر جا ممکن است poster بدهید. مدت صفر یا منفی نمایش داده نمی‌شود. مدت در گوشهٔ پایینِ سمت پایان صفحه است (در صفحهٔ راست‌به‌چپ پایین چپ)، پس با جایگاه aside کاشی در گوشهٔ شروع برخورد نمی‌کند. صوت در کارت یک نوار کوتاه است و در جزئیات پخش‌کننده دارد.

راهنمای استفاده

بکنید

  • width و height رسانه را هر جا معلوم است در مبدل پر کنید تا کادر از ابتدا درست باشد. ردیف API جست‌وجوی محتوا آن‌ها را ندارد: با گزینهٔ mediaSize در fromSearchContent بدهید؛ بدون آن هر مورد کادر مربع می‌گیرد.
  • برای ویدئو poster بدهید. برای تصویر، اگر نسخهٔ کوچک آن را دارید، آن را هم در poster بگذارید: ردیف و کاشی همان نسخهٔ کوچک را بارگذاری می‌کنند و کارت و جزئیات تصویر کامل را.
  • برای چند مورد اول صفحه priority بگذارید و برای بقیه نه.
  • پراکسی و امضای نشانی را یک بار با SocialMediaProvider انجام دهید، نه با ساختن نشانی در هر صفحه.

نکنید

  • تصویر را با object-cover یا کادر ثابت خودتان در کارت نبرید.
  • فهرست کارت‌ها را در تمام عرض صفحه نچینید: در ستون 1500 پیکسلی هر تصویر به سقف 500 پیکسل می‌رسد و دو طرفش نوار محو بزرگ می‌ماند. جای فهرست تک‌ستونی، ستون خبرخوان (حدود 680 پیکسل) است.
  • وضعیت رسانه را با جزء دیگری نسازید؛ status را تنظیم کنید.

Props

MediaFrame

Prop

Type

SocialMediaProvider

Prop

Type

نوع ورودی برای کامپوننتی که MediaFrame را در بر می‌گیرد

MediaFrameProps همان چیزی است که MediaFrame می‌پذیرد: ویژگی‌های مشترک (MediaFrameBaseProps، جدول بالا) به‌اضافهٔ ویژگی‌های یک چیدمان (MediaFrameLayoutProps). کامپوننتی که MediaFrame را در بر می‌گیرد ورودی‌اش را با MediaFrameProps تعریف کند، تا همان قاعده‌ها (مثلاً density فقط در row) برای آن هم برقرار بماند:

import { MediaFrame, type MediaFrameProps } from '@partodata/ui/social'

export function PostMedia(props: MediaFrameProps) {
  return <MediaFrame {...props} />
}

برای حذف یک ویژگی، آن را از MediaFrameBaseProps حذف کنید و MediaFrameLayoutProps را دوباره اضافه کنید (Omit<MediaFrameBaseProps, 'media'> & MediaFrameLayoutProps)؛ Omit روی خود MediaFrameProps چیدمان‌ها را در هم می‌ریزد و دادن نتیجه‌اش به MediaFrame کامپایل نمی‌شود.

دسترسی‌پذیری

  • تصویر بدون alt تزئینی است؛ وقتی متن پست محتوا را می‌گوید همین درست است. برای اینفوگرافی متن جایگزین بدهید.
  • نسخهٔ محوشدهٔ پس‌زمینه و نشانهٔ پخش از درخت دسترسی‌پذیری حذف‌اند؛ ویدئو با متن پنهان «ویدئو، مدت» اعلام می‌شود.
  • هر وضعیت یک نشانهٔ نام‌دار (role="img") دارد، پس صفحه‌خوان می‌گوید رسانه هست ولی بارگذاری نشده است.
  • دکمه‌های «تلاش دوباره» و «نمایش محتوای حساس» و پیوند «مشاهده در منبع» کلیک و کلیدهای Enter و Space را به کارت بیرونی نمی‌رسانند؛ Escape و کلیدهای جهت به فهرست بیرونی می‌رسند. پیوند منبع در زبانهٔ تازه باز می‌شود.
  • پس از «تلاش دوباره» یا «نمایش»، دکمه حذف می‌شود و فوکوس به خود رسانه (در جزئیات، به پخش‌کننده) می‌رود، نه به ابتدای صفحه.
  • ویدئوی حساس در جزئیات تا پیش از «نمایش» کنترل پخش ندارد و از ترتیب Tab بیرون است (inert).

کامپوننت‌های مرتبط

  • SocialText — متن همان پست؛ کنار بندانگشتی ردیف با حداکثر دو یا سه خط.
  • MetricGroup — شاخص‌های پست زیر رسانه، با «—» برای مقدار نامعلوم.
  • مدل محتوای اجتماعی — شکل SocialMedia، مبدل‌هایی که وضعیت رسانه را پر می‌کنند و گزینهٔ mediaSize برای ابعادی که API نمی‌فرستد.