فهرست منبع

fix(app): complete shared RTL primitives (#40388)

Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
opencode-agent[bot] 1 هفته پیش
والد
کامیت
8721a2710b

+ 11 - 6
packages/app/src/components/help-button.tsx

@@ -1,5 +1,6 @@
 import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
 import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
+import { isRTL } from "@kobalte/core/i18n"
 import { createSignal, Show } from "solid-js"
 import { Drawer, DrawerClose, DrawerContent } from "@/components/ui/drawer"
 import { usePlatform } from "@/context/platform"
@@ -19,18 +20,19 @@ export function TabsInfoPopup() {
   const language = useLanguage()
   const [drawerOpen, setDrawerOpen] = createSignal(false)
   const windows = () => platform.platform === "desktop" && platform.os === "windows"
+  const rtl = () => isRTL(language.intl())
 
   return (
-    <Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side="right">
+    <Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side={rtl() ? "left" : "right"}>
       <Show when={settings.general.shouldDisplayTabsToast()}>
         <div
-          class="fixed bottom-5 right-5 z-50 h-[240px] w-[192px] rounded-[8px] bg-v2-background-bg-base p-1 shadow-[var(--v2-elevation-floating)]"
+          class="fixed bottom-5 end-5 z-50 h-[240px] w-[192px] rounded-[8px] bg-v2-background-bg-base p-1 shadow-[var(--v2-elevation-floating)]"
           aria-label={language.t("help.tabs.toast.ariaLabel")}
         >
           <button
             type="button"
             aria-label={language.t("help.tabs.toast.dismiss")}
-            class="absolute top-3 right-3 z-10 size-5 flex items-center justify-center rounded-[4px] bg-[rgba(0,0,0,0.4)]"
+            class="absolute top-3 end-3 z-10 size-5 flex items-center justify-center rounded-[4px] bg-[rgba(0,0,0,0.4)]"
             onClick={settings.general.dismissTabsToast}
           >
             <svg
@@ -46,7 +48,7 @@ export function TabsInfoPopup() {
           </button>
           <button
             type="button"
-            class="relative block h-[232px] w-[184px] cursor-pointer overflow-hidden rounded-[4px] text-left"
+            class="relative block h-[232px] w-[184px] cursor-pointer overflow-hidden rounded-[4px] text-start"
             onClick={() => {
               settings.general.dismissTabsToast()
               setDrawerOpen(true)
@@ -77,7 +79,10 @@ export function TabsInfoPopup() {
         style={
           windows()
             ? {
-                inset: "0 0 0 auto",
+                top: "0",
+                bottom: "0",
+                "inset-inline-end": "0",
+                "inset-inline-start": "auto",
                 "max-height": "100vh",
                 "max-width": "100vw",
                 "border-radius": "0",
@@ -93,7 +98,7 @@ export function TabsInfoPopup() {
             variant="neutral"
             aria-label={language.t("common.close")}
             icon={<IconV2 name="xmark-small" />}
-            class="absolute top-[10px] left-[-36px]"
+            class="absolute top-[10px] start-[-36px]"
           />
         </Show>
         <div

+ 1 - 1
packages/app/src/components/titlebar.tsx

@@ -515,7 +515,7 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
                   <div
                     class="flex items-center shrink-0"
                     classList={{
-                      "-translate-x-[36px]": layout.sidebar.opened() && !!params.dir,
+                      "ltr:-translate-x-[36px] rtl:translate-x-[36px]": layout.sidebar.opened() && !!params.dir,
                       "duration-180 ease-out": !layout.sidebar.opened(),
                       "duration-180 ease-in": layout.sidebar.opened(),
                     }}

+ 1 - 1
packages/app/src/components/ui/drawer.tsx

@@ -55,7 +55,7 @@ const DrawerContent = <T extends ValidComponent = "div">(props: DynamicProps<T,
       <DrawerPrimitive.Content
         class={props.class}
         classList={{
-          "group/drawer-content fixed inset-y-[6px] right-[6px] left-auto z-[100] flex h-auto max-h-[calc(100vh-12px)] w-[560px] max-w-[calc(100vw-12px)] flex-col items-start rounded-[8px] bg-v2-background-bg-base p-0 shadow-[var(--v2-elevation-overlay)] data-[transitioning]:transition-transform data-[transitioning]:duration-300 md:select-none": true,
+          "group/drawer-content fixed inset-y-[6px] end-[6px] start-auto z-[100] flex h-auto max-h-[calc(100vh-12px)] w-[560px] max-w-[calc(100vw-12px)] flex-col items-start rounded-[8px] bg-v2-background-bg-base p-0 shadow-[var(--v2-elevation-overlay)] data-[transitioning]:transition-transform data-[transitioning]:duration-300 md:select-none": true,
         }}
         {...rest}
       >

+ 5 - 0
packages/ui/src/components/icon.css

@@ -32,3 +32,8 @@
     height: auto;
   }
 }
+
+:dir(rtl) [data-component="icon"][data-directional] [data-slot="icon-svg"],
+:dir(rtl) [data-slot="menu-v2-item-chevron"] {
+  transform: scaleX(-1);
+}

+ 5 - 1
packages/ui/src/components/icon.tsx

@@ -151,7 +151,11 @@ export function Icon(props: IconProps) {
   onMount(ensureSprite)
 
   return (
-    <div data-component="icon" data-size={local.size || "normal"}>
+    <div
+      data-component="icon"
+      data-size={local.size || "normal"}
+      data-directional={local.name === "chevron-left" || local.name === "chevron-right" ? true : undefined}
+    >
       <svg
         data-slot="icon-svg"
         classList={{

+ 1 - 1
packages/ui/src/components/toast.css

@@ -1,7 +1,7 @@
 [data-component="toast-region"] {
   position: fixed;
   bottom: 48px;
-  right: 32px;
+  inset-inline-end: 32px;
   z-index: 1000;
   display: flex;
   flex-direction: column;

+ 9 - 2
packages/ui/src/context/i18n.tsx

@@ -1,4 +1,5 @@
 import { createContext, useContext, type Accessor, type ParentProps } from "solid-js"
+import { I18nProvider } from "@kobalte/core/i18n"
 import { dict as en } from "../i18n/en"
 
 export type UiI18nKey = keyof typeof en
@@ -57,10 +58,16 @@ const fallback: UiI18n = {
 
 const Context = createContext<UiI18n>(fallback)
 
-export function I18nProvider(props: ParentProps<{ value: UiI18n }>) {
-  return <Context.Provider value={props.value}>{props.children}</Context.Provider>
+function UiI18nProvider(props: ParentProps<{ value: UiI18n }>) {
+  return (
+    <I18nProvider locale={props.value.locale()}>
+      <Context.Provider value={props.value}>{props.children}</Context.Provider>
+    </I18nProvider>
+  )
 }
 
+export { UiI18nProvider as I18nProvider }
+
 export function useI18n() {
   return useContext(Context)
 }

+ 3 - 2
packages/ui/src/v2/components/toast-v2.tsx

@@ -1,4 +1,5 @@
 import { Toaster, toast, type ToasterProps } from "solid-sonner"
+import { isRTL } from "@kobalte/core/i18n"
 import type { ComponentProps, JSX } from "solid-js"
 import { createContext, onCleanup, onMount, splitProps, useContext } from "solid-js"
 import { Portal } from "solid-js/web"
@@ -46,8 +47,8 @@ function ToastV2Region(props: ToastV2RegionProps) {
   return (
     <Portal>
       <Toaster
-        position="bottom-right"
-        offset={{ right: 32, bottom: 48 }}
+        position={isRTL(i18n.locale()) ? "bottom-left" : "bottom-right"}
+        offset={isRTL(i18n.locale()) ? { left: 32, bottom: 48 } : { right: 32, bottom: 48 }}
         mobileOffset={16}
         gap={12}
         duration={5000}