Yugen UI

Hướng chữ RTL

Yugen UI hỗ trợ ngôn ngữ viết từ phải sang trái (Arabic, Hebrew, Persian…) bằng một prop dir và quy tắc chỉ dùng utility logical.

Bật RTL

Một prop dir trên YugenProvider: thẻ bao (hoặc <html> khi applyToDocument) nhận dir, và DirectionProvider của Radix truyền hướng chữ cho mọi component.

// Toàn ứng dụng: ghi dir="rtl" lên <html> và cấp hướng chữ cho Radix
<YugenProvider applyToDocument dir="rtl" theme="light">
  <App />
</YugenProvider>

// Hoặc chỉ một vùng (ví dụ xem trước bản dịch)
<YugenProvider dir="rtl" lang="ar" className="rounded-xlarge p-5">
  <Breadcrumb items={[{ label: "الرئيسية" }, { label: "المستندات" }]} />
</YugenProvider>
<!-- Đặt sẵn trong HTML để trang không nhảy hướng khi tải -->
<html lang="ar" dir="rtl" data-theme="light">
  • CSS: thuộc tính dir đảo flex, grid, text-align mặc định và mọi utility logical (ms-*, ps-*, start-*…).
  • Hành vi: Tabs, Menu (menu con mở sang trái), RadioGroup, Slider, ToggleGroup, ScrollArea, Toolbar, Tree, List, Carousel, TagPicker, InputOTP, Rating đọc hướng từ DirectionProvider để đảo phím mũi tên và chiều kéo.
  • Overlay: Popover, Tooltip, HoverCard, Dialog, Drawer, Menu và listbox gắn dir của provider lên bề mặt dù render trong portal; Toaster của withToaster dùng vị trí logic bottom-end nên tự sang góc trái khi RTL.

Ví dụ trực tiếp

Đổi hướng chữ của khung bên dưới; thử Tab rồi dùng phím mũi tên trên Tabs và Slider — ← / → đổi vai trò khi RTL.

مرحبا بك في يوغن

مستوى الصوت30
إعدادات الحساب العامة.
<YugenProvider dir="rtl" lang="ar" className="flex flex-col gap-4 rounded-2xlarge border border-stroke-2 p-6">
  <Breadcrumb items={[{ label: "الرئيسية" }, { label: "المستندات" }, { label: "التقارير" }]} />
  <Input aria-label="ابحث" placeholder="ابحث…" contentBefore={<Search20Regular />} rootClassName="w-64" />
  <Switch label="الإشعارات" defaultChecked />
  <Slider label="مستوى الصوت" showValue defaultValue={[30]} rootClassName="max-w-sm" />
  <Pagination pageCount={10} defaultPage={3} />
  <Button appearance="primary" icon={<ArrowRight20Regular className="rtl:-scale-x-100" />} iconPosition="after">
    التالي
  </Button>
</YugenProvider>

Utility logical

Quy tắc cho tác giả component: không dùng hướng vật lý cho bố cục. Utility logical tự đảo theo dir, không cần variant rtl:.

Không dùng (vật lý)Dùng (logical)CSS
ml-* / mr-*ms-* / me-*margin-inline-start / end
pl-* / pr-*ps-* / pe-*padding-inline-start / end
left-* / right-*start-* / end-*inset-inline-start / end
border-l / border-rborder-s / border-eborder-inline-start / end
rounded-l-* / rounded-r-*rounded-s-* / rounded-e-*border-start-start-radius…
rounded-tl-* / rounded-tr-*rounded-ss-* / rounded-se-*border-start-start / start-end-radius
text-left / text-righttext-start / text-endtext-align: start / end
space-x-* với margin taygap-*gap không có hướng

Ngoại lệ hợp lệ: căn giữa bằng left-1/2 -translate-x-1/2 (đối xứng), và nội dung luôn LTR như mã nguồn, số điện thoại, URL — bọc bằng <bdi> hoặc dir="ltr" để thuật toán bidi không đảo thứ tự ký tự.

اتصل بنا: +84 912 345 678

الرمز: npm run dev

Lật icon & transform

Icon biểu thị hướng (tiến, lùi, gửi, thụt lề) phải lật trong RTL; icon biểu thị vật thể hay khái niệm thì giữ nguyên.

dir="ltr"

  • Mũi tên tiến / lùilật
  • Chevron điều hướnglật
  • Gửilật
  • Tìm kiếmgiữ
  • Dấu tíchgiữ
  • Phát mediagiữ

dir="rtl"

  • Mũi tên tiến / lùilật
  • Chevron điều hướnglật
  • Gửilật
  • Tìm kiếmgiữ
  • Dấu tíchgiữ
  • Phát mediagiữ
{/* Icon chỉ hướng: lật theo chiều đọc */}
<ChevronRight16Regular className="rtl:-scale-x-100" />
<Button icon={<ArrowLeft20Regular className="rtl:-scale-x-100" />}>Quay lại</Button>

{/* Transform vật lý: viết hai nhánh */}
<span className="translate-x-5.5 rtl:-translate-x-5.5" />

{/* Chevron mở / đóng: lật trước, rồi xoay ngược chiều trong RTL */}
<ChevronRight20Regular className="rtl:-scale-x-100 group-data-[state=open]:rotate-90 rtl:group-data-[state=open]:-rotate-90" />

Biến thể rtl: của Tailwind khớp phần tử nằm trong bất kỳ tổ tiên nào có dir="rtl", nên hoạt động cả với YugenProvider lồng. Dùng nó cho mọi giá trị vật lý không có bản logical: translate-x, rotate, scale-x, gradient có góc, background-position.

Phím mũi tên

Component Radix và các component Yugen có điều hướng ngang đã tự đảo ← / →. Khi tự viết, đọc hướng bằng useDirection().

import { useDirection } from "@radix-ui/react-direction";

/** ← / → theo chiều đọc: trong RTL, ArrowLeft là "tiến". */
export function useInlineArrowKeys(onPrev: () => void, onNext: () => void) {
  const dir = useDirection(); // "ltr" | "rtl" — lấy từ YugenProvider
  return (event: React.KeyboardEvent) => {
    const nextKey = dir === "rtl" ? "ArrowLeft" : "ArrowRight";
    const prevKey = dir === "rtl" ? "ArrowRight" : "ArrowLeft";
    if (event.key === nextKey) {
      event.preventDefault();
      onNext();
    } else if (event.key === prevKey) {
      event.preventDefault();
      onPrev();
    }
  };
}

useYugen().dir cho cùng giá trị nhưng useDirection() của @radix-ui/react-direction cũng hoạt động bên ngoài Yugen (bất kỳ DirectionProvider nào). ↑ / ↓, Home / End không đổi theo hướng chữ.

Kiểm thử

RTL hỏng thường lộ ra ở khoảng cách, icon và phím — kiểm tra cả ba.

  1. Bật nút LTR / RTL trên thanh trên cùng của site docs và lướt qua trang component của bạn.
  2. So khoảng cách hai bên: icon trong nút, padding của ô nhập, vạch chọn của Nav / Tabs phải đổi phía.
  3. Đi bằng bàn phím: ← / → trên Tabs, Slider, Toolbar, menu con, Carousel phải theo chiều đọc.
  4. Mở overlay (Menu, Popover, Tooltip, Drawer start / end) và Toast — chúng nằm trong portal.
  5. Tìm utility vật lý còn sót trong mã nguồn:
# Liệt kê class vật lý cần xem lại (bỏ qua kết quả căn giữa left-1/2)
rg -n "\b(ml|mr|pl|pr|left|right|border-l|border-r|rounded-l|rounded-r|text-left|text-right)-" src/components
Nên
  • Dùng ms/me/ps/pe/start/end/border-s/border-e/rounded-s/rounded-e/text-start.
  • Lật icon chỉ hướng bằng rtl:-scale-x-100.
  • Đặt lang cùng dir để trình đọc màn hình đọc đúng ngôn ngữ.
Không nên
  • Không lật logo, dấu tích, kính lúp, icon media.
  • Không viết nhánh dir === 'rtl' trong JS cho thứ CSS logical làm được.
  • Không giả định ArrowRight luôn là “tiếp theo”.