diff --git a/apps/ui/app/layout.tsx b/apps/ui/app/layout.tsx
index 3b39bc3b0..05c8b4168 100644
--- a/apps/ui/app/layout.tsx
+++ b/apps/ui/app/layout.tsx
@@ -7,7 +7,9 @@ import {
AnchoredToastProvider,
ToastProvider,
} from "@/registry/default/ui/toast";
+import { SiteDirectionProvider } from "@/components/direction-switcher";
import { SiteHeader } from "@/components/site-header";
+import { directionScript } from "@/lib/direction";
export const metadata: Metadata = {
description:
@@ -24,26 +26,31 @@ export default function RootLayout({
}>) {
return (
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/apps/ui/components/direction-switcher.tsx b/apps/ui/components/direction-switcher.tsx
new file mode 100644
index 000000000..b7a38f41f
--- /dev/null
+++ b/apps/ui/components/direction-switcher.tsx
@@ -0,0 +1,75 @@
+"use client";
+
+import { PilcrowLeftIcon, PilcrowRightIcon } from "lucide-react";
+import { type ReactNode, useSyncExternalStore } from "react";
+import { DirectionProvider } from "@/registry/default/base-ui/direction-provider";
+import { Button } from "@/registry/default/ui/button";
+import { DIRECTION_STORAGE_KEY } from "@/lib/direction";
+
+type Direction = "ltr" | "rtl";
+
+const listeners = new Set<() => void>();
+
+function applyDirection(direction: Direction) {
+ document.documentElement.dir = direction;
+ for (const listener of listeners) listener();
+}
+
+function onStorage(event: StorageEvent) {
+ if (event.key !== DIRECTION_STORAGE_KEY) return;
+ applyDirection(event.newValue === "rtl" ? "rtl" : "ltr");
+}
+
+function subscribe(listener: () => void) {
+ if (listeners.size === 0) window.addEventListener("storage", onStorage);
+ listeners.add(listener);
+ return () => {
+ listeners.delete(listener);
+ if (listeners.size === 0) window.removeEventListener("storage", onStorage);
+ };
+}
+
+function getDirection(): Direction {
+ return document.documentElement.dir === "rtl" ? "rtl" : "ltr";
+}
+
+function getServerDirection(): Direction {
+ return "ltr";
+}
+
+function setDirection(direction: Direction) {
+ try {
+ localStorage.setItem(DIRECTION_STORAGE_KEY, direction);
+ } catch {}
+ applyDirection(direction);
+}
+
+function useSiteDirection() {
+ return useSyncExternalStore(subscribe, getDirection, getServerDirection);
+}
+
+export function SiteDirectionProvider({ children }: { children: ReactNode }) {
+ const direction = useSiteDirection();
+
+ return (
+ {children}
+ );
+}
+
+export function DirectionSwitcher() {
+ const isRtl = useSiteDirection() === "rtl";
+
+ return (
+
+ );
+}
diff --git a/apps/ui/components/site-header.tsx b/apps/ui/components/site-header.tsx
index 52cf2e81d..a206af039 100644
--- a/apps/ui/components/site-header.tsx
+++ b/apps/ui/components/site-header.tsx
@@ -1,6 +1,7 @@
import { Separator } from "@coss/ui/components/separator";
import { SiteHeader as WorkspaceSiteHeader } from "@coss/ui/shared/site-header";
import { CommandMenu } from "@/components/command-menu";
+import { DirectionSwitcher } from "@/components/direction-switcher";
import { MainNav } from "@/components/main-nav";
import { MobileNav } from "@/components/mobile-nav";
import { appConfig } from "@/lib/config";
@@ -11,6 +12,7 @@ export function SiteHeader() {
return (
}
currentProduct="ui"
mobileNav={
= {\n default: \"h-8.5 px-[calc(--spacing(2.5)-1px)] sm:h-7.5\",\n lg: \"h-9.5 px-[calc(--spacing(3)-1px)] sm:h-8.5\",\n sm: \"h-7.5 px-[calc(--spacing(2)-1px)] sm:h-6.5\",\n};\n\nexport const segmentedControlItemLayoutClassName =\n \"gap-1.5 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0\";\n\nexport const segmentedControlIndicatorSurfaceClassName =\n \"rounded-md bg-background shadow-sm/5 dark:bg-input\";\n\nconst SegmentedControlContext: React.Context =\n React.createContext(\"default\");\n\nexport function SegmentedControl({\n className,\n children,\n render,\n size = \"default\",\n ...props\n}: useRender.ComponentProps<\"div\"> & {\n size?: SegmentedControlSize;\n}): React.ReactElement {\n const defaultProps = {\n children: (\n <>\n {children}\n \n >\n ),\n className: cn(\n \"relative z-0 flex w-fit items-center justify-center gap-0.5 rounded-lg bg-muted p-0.5\",\n className,\n ),\n \"data-size\": size,\n \"data-slot\": \"segmented-control\",\n };\n\n const element = useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n\n return (\n \n {element}\n \n );\n}\n\nexport function SegmentedControlItem({\n className,\n render,\n size,\n ...props\n}: useRender.ComponentProps<\"button\"> & {\n size?: SegmentedControlSize;\n}): React.ReactElement {\n const contextSize = React.useContext(SegmentedControlContext);\n const resolvedSize = size ?? contextSize;\n\n const defaultProps = {\n className: cn(\n \"relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center whitespace-nowrap rounded-md border border-transparent font-medium text-base text-muted-foreground/72 outline-2 outline-transparent transition-[color,outline-color] hover:bg-transparent hover:text-muted-foreground focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-64 data-disabled:pointer-events-none data-disabled:opacity-64 sm:text-sm\",\n \"data-checked:bg-background data-checked:text-foreground data-checked:shadow-sm/5 dark:data-checked:bg-input\",\n \"aria-[current=page]:bg-background aria-[current=page]:text-foreground aria-[current=page]:shadow-sm/5 dark:aria-[current=page]:bg-input\",\n \"data-pressed:bg-background data-pressed:text-foreground data-pressed:shadow-sm/5 dark:data-pressed:bg-input\",\n \"[:has(>[data-slot=segmented-control-indicator]:not([hidden]))>&]:bg-transparent! [:has(>[data-slot=segmented-control-indicator]:not([hidden]))>&]:shadow-none!\",\n segmentedControlItemLayoutClassName,\n segmentedControlItemSizeClassNames[resolvedSize],\n className,\n ),\n \"data-size\": resolvedSize,\n \"data-slot\": \"segmented-control-item\",\n ...(render ? {} : { type: \"button\" as const }),\n };\n\n return useRender({\n defaultTagName: \"button\",\n props: mergeProps<\"button\">(defaultProps, props),\n render,\n });\n}\n\nconst activeItemSelector =\n ':scope > [data-checked], :scope > [aria-current=\"page\"], :scope > [data-pressed]';\n\nconst observedAttributes: string[] = [\n \"data-checked\",\n \"aria-current\",\n \"data-pressed\",\n];\n\nconst MAX_LAYOUT_ROUNDING_ERROR = 2;\n\ninterface ActiveItemRect {\n height: number;\n left: number;\n top: number;\n width: number;\n}\n\nfunction isSameRect(\n a: ActiveItemRect | null,\n b: ActiveItemRect | null,\n): boolean {\n return (\n a?.left === b?.left &&\n a?.top === b?.top &&\n a?.width === b?.width &&\n a?.height === b?.height\n );\n}\n\nfunction getCssSize(element: HTMLElement): { height: number; width: number } {\n const style = getComputedStyle(element);\n const width = Number.parseFloat(style.width) || 0;\n const height = Number.parseFloat(style.height) || 0;\n if (\n Math.round(width) !== element.offsetWidth ||\n Math.round(height) !== element.offsetHeight\n ) {\n return { height: element.offsetHeight, width: element.offsetWidth };\n }\n return { height, width };\n}\n\nfunction measureItem(item: HTMLElement, root: HTMLElement): ActiveItemRect {\n const { width, height } = getCssSize(item);\n const rootSize = getCssSize(root);\n const itemRect = item.getBoundingClientRect();\n const rootRect = root.getBoundingClientRect();\n const scaleX = rootSize.width > 0 ? rootRect.width / rootSize.width : 1;\n const scaleY = rootSize.height > 0 ? rootRect.height / rootSize.height : 1;\n\n const rectLeft =\n (itemRect.left - rootRect.left) / scaleX -\n root.clientLeft +\n root.scrollLeft;\n const rectTop =\n (itemRect.top - rootRect.top) / scaleY - root.clientTop + root.scrollTop;\n\n const isUndistorted =\n Math.abs(rectLeft - item.offsetLeft) <= MAX_LAYOUT_ROUNDING_ERROR &&\n Math.abs(rectTop - item.offsetTop) <= MAX_LAYOUT_ROUNDING_ERROR;\n\n return {\n height,\n left: isUndistorted ? rectLeft : item.offsetLeft,\n top: isUndistorted ? rectTop : item.offsetTop,\n width,\n };\n}\n\nfunction SegmentedControlIndicator(): React.ReactElement {\n const ref = React.useRef(null);\n const [rect, setRect] = React.useState(null);\n\n React.useLayoutEffect(() => {\n const root = ref.current?.parentElement;\n if (!root) {\n return;\n }\n\n const update = (): void => {\n const item = root.querySelector(activeItemSelector);\n const next = item ? measureItem(item, root) : null;\n setRect((previous) => (isSameRect(previous, next) ? previous : next));\n };\n\n update();\n\n const resizeObserver = new ResizeObserver(update);\n const mutationObserver = new MutationObserver((records) => {\n if (records.some((record) => record.type === \"childList\")) {\n observeItems();\n }\n update();\n });\n const observeItems = (): void => {\n resizeObserver.disconnect();\n resizeObserver.observe(root);\n for (const child of root.children) {\n resizeObserver.observe(child);\n mutationObserver.observe(child, {\n attributeFilter: observedAttributes,\n attributes: true,\n });\n }\n };\n mutationObserver.observe(root, { childList: true });\n observeItems();\n\n return () => {\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n };\n }, []);\n\n return (\n \n );\n}\n",
+ "content": "\"use client\";\n\nimport { useDirection } from \"@base-ui/react/direction-provider\";\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport * as React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\n\nexport type SegmentedControlSize = \"default\" | \"lg\" | \"sm\";\n\nexport const segmentedControlItemSizeClassNames: Record<\n SegmentedControlSize,\n string\n> = {\n default: \"h-8.5 px-[calc(--spacing(2.5)-1px)] sm:h-7.5\",\n lg: \"h-9.5 px-[calc(--spacing(3)-1px)] sm:h-8.5\",\n sm: \"h-7.5 px-[calc(--spacing(2)-1px)] sm:h-6.5\",\n};\n\nexport const segmentedControlItemLayoutClassName =\n \"gap-1.5 [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:-mx-0.5 [&_svg]:shrink-0\";\n\nexport const segmentedControlIndicatorSurfaceClassName =\n \"rounded-md bg-background shadow-sm/5 dark:bg-input\";\n\nconst SegmentedControlContext: React.Context =\n React.createContext(\"default\");\n\nexport function SegmentedControl({\n className,\n children,\n render,\n size = \"default\",\n ...props\n}: useRender.ComponentProps<\"div\"> & {\n size?: SegmentedControlSize;\n}): React.ReactElement {\n const direction = useDirection();\n\n const defaultProps = {\n children: (\n <>\n {children}\n \n >\n ),\n className: cn(\n \"relative z-0 flex w-fit items-center justify-center gap-0.5 rounded-lg bg-muted p-0.5\",\n className,\n ),\n \"data-size\": size,\n \"data-slot\": \"segmented-control\",\n };\n\n const element = useRender({\n defaultTagName: \"div\",\n props: mergeProps<\"div\">(defaultProps, props),\n render,\n });\n\n return (\n \n {element}\n \n );\n}\n\nexport function SegmentedControlItem({\n className,\n render,\n size,\n ...props\n}: useRender.ComponentProps<\"button\"> & {\n size?: SegmentedControlSize;\n}): React.ReactElement {\n const contextSize = React.useContext(SegmentedControlContext);\n const resolvedSize = size ?? contextSize;\n\n const defaultProps = {\n className: cn(\n \"relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center whitespace-nowrap rounded-md border border-transparent font-medium text-base text-muted-foreground/72 outline-2 outline-transparent transition-[color,outline-color] hover:bg-transparent hover:text-muted-foreground focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-64 data-disabled:pointer-events-none data-disabled:opacity-64 sm:text-sm\",\n \"data-checked:bg-background data-checked:text-foreground data-checked:shadow-sm/5 dark:data-checked:bg-input\",\n \"aria-[current=page]:bg-background aria-[current=page]:text-foreground aria-[current=page]:shadow-sm/5 dark:aria-[current=page]:bg-input\",\n \"data-pressed:bg-background data-pressed:text-foreground data-pressed:shadow-sm/5 dark:data-pressed:bg-input\",\n \"[:has(>[data-slot=segmented-control-indicator]:not([hidden]))>&]:bg-transparent! [:has(>[data-slot=segmented-control-indicator]:not([hidden]))>&]:shadow-none!\",\n segmentedControlItemLayoutClassName,\n segmentedControlItemSizeClassNames[resolvedSize],\n className,\n ),\n \"data-size\": resolvedSize,\n \"data-slot\": \"segmented-control-item\",\n ...(render ? {} : { type: \"button\" as const }),\n };\n\n return useRender({\n defaultTagName: \"button\",\n props: mergeProps<\"button\">(defaultProps, props),\n render,\n });\n}\n\nconst activeItemSelector =\n ':scope > [data-checked], :scope > [aria-current=\"page\"], :scope > [data-pressed]';\n\nconst observedAttributes: string[] = [\n \"data-checked\",\n \"aria-current\",\n \"data-pressed\",\n];\n\nconst MAX_LAYOUT_ROUNDING_ERROR = 2;\n\ninterface ActiveItemRect {\n height: number;\n left: number;\n top: number;\n width: number;\n}\n\nfunction isSameRect(\n a: ActiveItemRect | null,\n b: ActiveItemRect | null,\n): boolean {\n return (\n a?.left === b?.left &&\n a?.top === b?.top &&\n a?.width === b?.width &&\n a?.height === b?.height\n );\n}\n\nfunction getCssSize(element: HTMLElement): { height: number; width: number } {\n const style = getComputedStyle(element);\n const width = Number.parseFloat(style.width) || 0;\n const height = Number.parseFloat(style.height) || 0;\n if (\n Math.round(width) !== element.offsetWidth ||\n Math.round(height) !== element.offsetHeight\n ) {\n return { height: element.offsetHeight, width: element.offsetWidth };\n }\n return { height, width };\n}\n\nfunction measureItem(item: HTMLElement, root: HTMLElement): ActiveItemRect {\n const { width, height } = getCssSize(item);\n const rootSize = getCssSize(root);\n const itemRect = item.getBoundingClientRect();\n const rootRect = root.getBoundingClientRect();\n const scaleX = rootSize.width > 0 ? rootRect.width / rootSize.width : 1;\n const scaleY = rootSize.height > 0 ? rootRect.height / rootSize.height : 1;\n\n const rectLeft =\n (itemRect.left - rootRect.left) / scaleX -\n root.clientLeft +\n root.scrollLeft;\n const rectTop =\n (itemRect.top - rootRect.top) / scaleY - root.clientTop + root.scrollTop;\n\n const isUndistorted =\n Math.abs(rectLeft - item.offsetLeft) <= MAX_LAYOUT_ROUNDING_ERROR &&\n Math.abs(rectTop - item.offsetTop) <= MAX_LAYOUT_ROUNDING_ERROR;\n\n return {\n height,\n left: isUndistorted ? rectLeft : item.offsetLeft,\n top: isUndistorted ? rectTop : item.offsetTop,\n width,\n };\n}\n\nfunction SegmentedControlIndicator(): React.ReactElement {\n const ref = React.useRef(null);\n const [rect, setRect] = React.useState(null);\n\n React.useLayoutEffect(() => {\n const root = ref.current?.parentElement;\n if (!root) {\n return;\n }\n\n const update = (): void => {\n const item = root.querySelector(activeItemSelector);\n const next = item ? measureItem(item, root) : null;\n setRect((previous) => (isSameRect(previous, next) ? previous : next));\n };\n\n update();\n\n const resizeObserver = new ResizeObserver(update);\n const mutationObserver = new MutationObserver((records) => {\n if (records.some((record) => record.type === \"childList\")) {\n observeItems();\n }\n update();\n });\n const observeItems = (): void => {\n resizeObserver.disconnect();\n resizeObserver.observe(root);\n for (const child of root.children) {\n resizeObserver.observe(child);\n mutationObserver.observe(child, {\n attributeFilter: observedAttributes,\n attributes: true,\n });\n }\n };\n mutationObserver.observe(root, { childList: true });\n observeItems();\n\n return () => {\n resizeObserver.disconnect();\n mutationObserver.disconnect();\n };\n }, []);\n\n return (\n \n );\n}\n",
"type": "registry:ui"
}
],
diff --git a/apps/ui/public/r/tabs.json b/apps/ui/public/r/tabs.json
index aeb2b0ced..aad79e44e 100644
--- a/apps/ui/public/r/tabs.json
+++ b/apps/ui/public/r/tabs.json
@@ -10,7 +10,7 @@
"files": [
{
"path": "registry/default/ui/tabs.tsx",
- "content": "\"use client\";\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport * as React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport {\n type SegmentedControlSize,\n segmentedControlIndicatorSurfaceClassName,\n segmentedControlItemLayoutClassName,\n segmentedControlItemSizeClassNames,\n} from \"@/registry/default/ui/segmented-control\";\n\ntype TabsVariant = \"default\" | \"underline\";\ntype TabsSize = SegmentedControlSize;\n\nconst TabsListContext: React.Context =\n React.createContext(\"default\");\n\nexport function Tabs({\n className,\n ...props\n}: TabsPrimitive.Root.Props): React.ReactElement {\n return (\n \n );\n}\n\nexport function TabsList({\n variant = \"default\",\n size = \"default\",\n className,\n children,\n ...props\n}: TabsPrimitive.List.Props & {\n size?: TabsSize;\n variant?: TabsVariant;\n}): React.ReactElement {\n return (\n \n \n {children}\n \n \n \n );\n}\n\nexport function TabsTab({\n className,\n size,\n ...props\n}: TabsPrimitive.Tab.Props & {\n size?: TabsSize;\n}): React.ReactElement {\n const contextSize: TabsSize = React.useContext(TabsListContext);\n const resolvedSize: TabsSize = size ?? contextSize;\n\n return (\n \n );\n}\n\nexport function TabsPanel({\n className,\n ...props\n}: TabsPrimitive.Panel.Props): React.ReactElement {\n return (\n \n );\n}\n\nexport {\n TabsPrimitive,\n TabsTab as TabsTrigger,\n TabsPanel as TabsContent,\n type TabsSize,\n type TabsVariant,\n};\n",
+ "content": "\"use client\";\n\nimport { useDirection } from \"@base-ui/react/direction-provider\";\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport * as React from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport {\n type SegmentedControlSize,\n segmentedControlIndicatorSurfaceClassName,\n segmentedControlItemLayoutClassName,\n segmentedControlItemSizeClassNames,\n} from \"@/registry/default/ui/segmented-control\";\n\ntype TabsVariant = \"default\" | \"underline\";\ntype TabsSize = SegmentedControlSize;\n\nconst TabsListContext: React.Context =\n React.createContext(\"default\");\n\nexport function Tabs({\n className,\n ...props\n}: TabsPrimitive.Root.Props): React.ReactElement {\n return (\n \n );\n}\n\nexport function TabsList({\n variant = \"default\",\n size = \"default\",\n className,\n children,\n ...props\n}: TabsPrimitive.List.Props & {\n size?: TabsSize;\n variant?: TabsVariant;\n}): React.ReactElement {\n const direction = useDirection();\n\n return (\n \n \n {children}\n \n \n \n );\n}\n\nexport function TabsTab({\n className,\n size,\n ...props\n}: TabsPrimitive.Tab.Props & {\n size?: TabsSize;\n}): React.ReactElement {\n const contextSize: TabsSize = React.useContext(TabsListContext);\n const resolvedSize: TabsSize = size ?? contextSize;\n\n return (\n \n );\n}\n\nexport function TabsPanel({\n className,\n ...props\n}: TabsPrimitive.Panel.Props): React.ReactElement {\n return (\n \n );\n}\n\nexport {\n TabsPrimitive,\n TabsTab as TabsTrigger,\n TabsPanel as TabsContent,\n type TabsSize,\n type TabsVariant,\n};\n",
"type": "registry:ui"
}
],
diff --git a/apps/ui/registry/default/ui/segmented-control.tsx b/apps/ui/registry/default/ui/segmented-control.tsx
index f234e049c..8a09d305b 100644
--- a/apps/ui/registry/default/ui/segmented-control.tsx
+++ b/apps/ui/registry/default/ui/segmented-control.tsx
@@ -1,5 +1,6 @@
"use client";
+import { useDirection } from "@base-ui/react/direction-provider";
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import * as React from "react";
@@ -34,11 +35,13 @@ export function SegmentedControl({
}: useRender.ComponentProps<"div"> & {
size?: SegmentedControlSize;
}): React.ReactElement {
+ const direction = useDirection();
+
const defaultProps = {
children: (
<>
{children}
-
+
>
),
className: cn(
diff --git a/apps/ui/registry/default/ui/tabs.tsx b/apps/ui/registry/default/ui/tabs.tsx
index 48285576b..dce4f5178 100644
--- a/apps/ui/registry/default/ui/tabs.tsx
+++ b/apps/ui/registry/default/ui/tabs.tsx
@@ -1,5 +1,6 @@
"use client";
+import { useDirection } from "@base-ui/react/direction-provider";
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
import * as React from "react";
import { cn } from "@/registry/default/lib/utils";
@@ -42,6 +43,8 @@ export function TabsList({
size?: TabsSize;
variant?: TabsVariant;
}): React.ReactElement {
+ const direction = useDirection();
+
return (
);
diff --git a/packages/ui/src/components/segmented-control.tsx b/packages/ui/src/components/segmented-control.tsx
index 245ffe718..7b0bf7f54 100644
--- a/packages/ui/src/components/segmented-control.tsx
+++ b/packages/ui/src/components/segmented-control.tsx
@@ -1,5 +1,6 @@
"use client";
+import { useDirection } from "@base-ui/react/direction-provider";
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cn } from "@coss/ui/lib/utils";
@@ -34,11 +35,13 @@ export function SegmentedControl({
}: useRender.ComponentProps<"div"> & {
size?: SegmentedControlSize;
}): React.ReactElement {
+ const direction = useDirection();
+
const defaultProps = {
children: (
<>
{children}
-
+
>
),
className: cn(
diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx
index c30e221a4..668246cf2 100644
--- a/packages/ui/src/components/tabs.tsx
+++ b/packages/ui/src/components/tabs.tsx
@@ -1,5 +1,6 @@
"use client";
+import { useDirection } from "@base-ui/react/direction-provider";
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs";
import {
type SegmentedControlSize,
@@ -42,6 +43,8 @@ export function TabsList({
size?: TabsSize;
variant?: TabsVariant;
}): React.ReactElement {
+ const direction = useDirection();
+
return (
);
diff --git a/packages/ui/src/shared/site-header.tsx b/packages/ui/src/shared/site-header.tsx
index cba7161ab..53a9a4f88 100644
--- a/packages/ui/src/shared/site-header.tsx
+++ b/packages/ui/src/shared/site-header.tsx
@@ -7,11 +7,13 @@ import Link from "next/link";
export function SiteHeader({
mobileNav,
children,
+ actions,
currentProduct,
github,
}: {
mobileNav?: React.ReactNode;
children?: React.ReactNode;
+ actions?: React.ReactNode;
currentProduct?: string;
/** The product's own repository for the GitHub link and its stars. */
github?: { repo: string; href: string };
@@ -42,6 +44,7 @@ export function SiteHeader({
{children}
+ {actions}