Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions scripts/rtl-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"src/components/AdditionalFields/AdditionalFields.tsx": 2,
"src/components/Alert/Alert.tsx": 4,
"src/components/AllergyList/AllergyList.tsx": 1,
"src/components/AppHeader/AppHeader.tsx": 5,
"src/components/Assessment/Assessment.tsx": 6,
"src/components/AudioPlayer/AudioPlayer.tsx": 1,
"src/components/AuthDialog/AuthDialog.tsx": 9,
Expand All @@ -20,7 +19,6 @@
"src/components/BookingDialog/BookingDialog.tsx": 3,
"src/components/BusinessHours/BusinessHours.tsx": 2,
"src/components/BusinessHoursEditor/BusinessHoursEditor.tsx": 2,
"src/components/Card/Card.tsx": 6,
"src/components/CheckrIntegration/CheckrIntegration.tsx": 7,
"src/components/ClaimProviderForm/ClaimProviderForm.tsx": 2,
"src/components/CodeLookup/CodeLookup.tsx": 5,
Expand Down Expand Up @@ -104,8 +102,6 @@
"src/components/ServicePicker/ServicePicker.tsx": 6,
"src/components/ServicePricingManager/ServicePricingManager.tsx": 6,
"src/components/SetupServiceModal/SetupServiceModal.tsx": 8,
"src/components/Sheet/Sheet.tsx": 4,
"src/components/Sidebar/Sidebar.tsx": 14,
"src/components/SiteFooter/SiteFooter.tsx": 2,
"src/components/SiteHeader/SiteHeader.tsx": 10,
"src/components/Slider/Slider.tsx": 1,
Expand All @@ -115,7 +111,6 @@
"src/components/SuperChat/plugins/code.tsx": 1,
"src/components/SuperChat/plugins/nitroTable.tsx": 1,
"src/components/SuperChat/render/createMarkdownRenderer.tsx": 1,
"src/components/Table/Table.tsx": 3,
"src/components/TableOfContents/TableOfContents.tsx": 4,
"src/components/Timeline/Timeline.tsx": 1,
"src/components/Toast/Toast.tsx": 4,
Expand Down
10 changes: 5 additions & 5 deletions src/components/AppHeader/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ export function AppHeaderSection({
data-slot="app-header-section"
className={cn(
'flex items-center gap-3',
align === 'left' && 'mr-auto',
align === 'left' && 'me-auto',
align === 'center' && 'mx-auto',
align === 'right' && 'ml-auto',
align === 'right' && 'ms-auto',
className
)}
>
Expand Down Expand Up @@ -238,7 +238,7 @@ export function AppHeaderIconButton({
{typeof badge === 'number' && badge > 0 && (
<span
className={cn(
'absolute -top-1 -right-1 flex items-center justify-center',
'absolute -end-1 -top-1 flex items-center justify-center',
'h-[18px] min-w-[18px] px-1 text-[10px] font-bold',
'rounded-full bg-red-700 text-white'
)}
Expand Down Expand Up @@ -314,7 +314,7 @@ export function AppHeaderSearch({
)}
>
<SearchIcon />
<span className="flex-1 text-left whitespace-nowrap">{placeholder}</span>
<span className="flex-1 text-start whitespace-nowrap">{placeholder}</span>
<kbd
className={cn(
'hidden items-center gap-0.5 px-2 py-0.5 sm:inline-flex',
Expand Down Expand Up @@ -404,7 +404,7 @@ export function AppHeaderUserMenu({
</div>

{/* Name (hidden on small screens) */}
<div className="hidden min-w-0 text-left lg:block">
<div className="hidden min-w-0 text-start lg:block">
<div
data-slot="app-header-user-name"
className="max-w-[150px] truncate text-sm font-medium text-gray-900 dark:text-white"
Expand Down
37 changes: 28 additions & 9 deletions src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const cardVariants = cva(
}
);

const cardAccentVariants = cva('absolute left-0 top-0 bottom-0 w-1', {
const cardAccentVariants = cva('absolute start-0 top-0 bottom-0 w-1', {
variants: {
color: {
primary: 'bg-primary-800',
Expand All @@ -71,7 +71,7 @@ export interface CardProps
VariantProps<typeof cardVariants> {
/** Semantic HTML element to render as */
as?: 'div' | 'article' | 'section' | 'aside';
/** Accent color bar on the left side */
/** Accent color bar on the start (reading-direction) side */
accent?: 'primary' | 'success' | 'warning' | 'destructive' | 'info';
/** Loading state - shows skeleton overlay */
loading?: boolean;
Expand Down Expand Up @@ -118,7 +118,7 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>(
selected,
orientation,
}),
accent && 'pl-4',
accent && 'ps-4',
className
)}
data-slot="card"
Expand Down Expand Up @@ -296,8 +296,21 @@ CardMedia.displayName = 'CardMedia';
export interface CardBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
/** Badge color variant */
variant?: 'default' | 'primary' | 'success' | 'warning' | 'destructive';
/** Position of the badge */
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
/**
* Position of the badge. All values are direction-aware (logical):
* `*-left`/`*-right` are aliases for `*-start`/`*-end` and mirror in RTL
* documents. Prefer `start`/`end` values for clarity; pass a physical
* inset via `className` if fixed positioning is required.
*/
position?:
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right'
| 'top-start'
| 'top-end'
| 'bottom-start'
| 'bottom-end';
}

const CardBadge = React.forwardRef<HTMLSpanElement, CardBadgeProps>(
Expand All @@ -319,11 +332,17 @@ const CardBadge = React.forwardRef<HTMLSpanElement, CardBadgeProps>(
destructive: 'bg-destructive text-destructive-foreground',
};

// Direction-aware (logical) positions: `left`/`right` are aliases for
// `start`/`end` and mirror in RTL documents.
const positionClasses = {
'top-left': 'top-2 left-2',
'top-right': 'top-2 right-2',
'bottom-left': 'bottom-2 left-2',
'bottom-right': 'bottom-2 right-2',
'top-left': 'top-2 start-2',
'top-right': 'top-2 end-2',
'bottom-left': 'bottom-2 start-2',
'bottom-right': 'bottom-2 end-2',
'top-start': 'top-2 start-2',
'top-end': 'top-2 end-2',
'bottom-start': 'bottom-2 start-2',
'bottom-end': 'bottom-2 end-2',
};

return (
Expand Down
25 changes: 25 additions & 0 deletions src/components/Sheet/Sheet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,29 @@ describe('Sheet', () => {
fireEvent.keyDown(document, { key: 'Escape' });
expect(onOpenChange).not.toHaveBeenCalled();
});

describe('side variants (logical properties)', () => {
it.each([
['start', ['start-0', 'border-e']],
['end', ['end-0', 'border-s']],
] as const)('side="%s" positions via logical classes', (side, classes) => {
renderSheet({ side });
const dialog = screen.getByRole('dialog');
classes.forEach((c) => expect(dialog).toHaveClass(c));
});

it.each([
['left', 'start'],
['right', 'end'],
] as const)(
'side="%s" is a direction-aware alias for "%s"',
(physical, logical) => {
const { unmount } = renderSheet({ side: physical });
const physicalClasses = screen.getByRole('dialog').className;
unmount();
renderSheet({ side: logical });
expect(screen.getByRole('dialog').className).toBe(physicalClasses);
}
);
});
});
8 changes: 6 additions & 2 deletions src/components/Sheet/Sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ const sheetContentVariants = cva(
],
{
variants: {
// `left`/`right` are direction-aware aliases for `start`/`end` and
// mirror in RTL documents.
side: {
top: 'inset-x-0 top-0 border-b max-h-dvh',
bottom: 'inset-x-0 bottom-0 border-t max-h-dvh',
left: 'inset-y-0 left-0 h-full border-r w-3/4 sm:max-w-sm',
right: 'inset-y-0 right-0 h-full border-l w-3/4 sm:max-w-sm',
left: 'inset-y-0 start-0 h-full border-e w-3/4 sm:max-w-sm',
right: 'inset-y-0 end-0 h-full border-s w-3/4 sm:max-w-sm',
start: 'inset-y-0 start-0 h-full border-e w-3/4 sm:max-w-sm',
end: 'inset-y-0 end-0 h-full border-s w-3/4 sm:max-w-sm',
Comment thread
garrity-miepub marked this conversation as resolved.
},
},
defaultVariants: {
Expand Down
32 changes: 17 additions & 15 deletions src/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,14 @@ export function Sidebar({
data-testid={testId}
className={cn(
'flex h-screen flex-col',
'border-r border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-900',
'border-e border-neutral-200 bg-white dark:border-neutral-700 dark:bg-neutral-900',
'transition-all duration-300 ease-in-out',
// Mobile positioning
isMobileViewport && 'fixed top-0 left-0 z-50',
// Mobile positioning (start-pinned; off-canvas direction flips in RTL)
isMobileViewport && 'fixed start-0 top-0 z-50',
isMobileViewport &&
(isMobileOpen ? 'translate-x-0' : '-translate-x-full'),
(isMobileOpen
? 'translate-x-0'
: '-translate-x-full rtl:translate-x-full'),
// Desktop positioning
!isMobileViewport && 'relative',
className
Expand Down Expand Up @@ -219,7 +221,7 @@ export function SidebarHeader({
{showMobileClose && isMobileViewport && (
<button
onClick={closeMobile}
className="-mr-2 rounded-lg p-2 text-neutral-500 transition-colors hover:bg-neutral-100 lg:hidden dark:hover:bg-neutral-800"
className="-me-2 rounded-lg p-2 text-neutral-500 transition-colors hover:bg-neutral-100 lg:hidden dark:hover:bg-neutral-800"
aria-label="Close navigation"
>
<XIcon />
Expand Down Expand Up @@ -370,18 +372,18 @@ export function SidebarNavGroup({
<span
className={cn(
'text-muted-foreground h-5 w-5 flex-shrink-0',
!showCollapsed && 'mr-3'
!showCollapsed && 'me-3'
)}
>
{icon}
</span>
)}
{!showCollapsed && (
<>
<span className="flex-1 truncate text-left">{label}</span>
<span className="flex-1 truncate text-start">{label}</span>
<span
className={cn(
'ml-2 flex-shrink-0 transition-transform duration-200',
'ms-2 flex-shrink-0 transition-transform duration-200',
effectiveExpanded && 'rotate-180'
)}
>
Expand All @@ -401,7 +403,7 @@ export function SidebarNavGroup({
: 'max-h-0 opacity-0'
)}
>
<div className="pl-2">{children}</div>
<div className="ps-2">{children}</div>
</div>
)}
</div>
Expand Down Expand Up @@ -465,19 +467,19 @@ export function SidebarNavItem({
isActive
? 'text-primary-800 dark:text-primary-400'
: 'text-muted-foreground',
!showCollapsed && 'mr-3'
!showCollapsed && 'me-3'
)}
>
{icon}
</span>
)}
{!showCollapsed && (
<>
<span className="flex-1 truncate text-left">{label}</span>
<span className="flex-1 truncate text-start">{label}</span>
{badge && (
<span
className={cn(
'ml-2 rounded-full px-2 py-0.5 text-xs font-medium',
'ms-2 rounded-full px-2 py-0.5 text-xs font-medium',
isActive
? 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300'
: 'bg-neutral-100 text-neutral-600 dark:bg-neutral-700 dark:text-neutral-400'
Expand Down Expand Up @@ -569,7 +571,7 @@ export function SidebarToggle({

if (position === 'floating') {
return (
<div className="absolute top-6 -right-3 z-10 rounded-full border border-neutral-200 bg-white shadow-md dark:border-neutral-700 dark:bg-neutral-900">
<div className="absolute -end-3 top-6 z-10 rounded-full border border-neutral-200 bg-white shadow-md dark:border-neutral-700 dark:bg-neutral-900">
{button}
</div>
);
Expand Down Expand Up @@ -679,7 +681,7 @@ export function SidebarSearch({
return (
<div data-slot="sidebar-search" className={cn('px-3 py-2', className)}>
<div className="relative">
<div className="absolute top-1/2 left-3 -translate-y-1/2 text-neutral-400">
<div className="absolute start-3 top-1/2 -translate-y-1/2 text-neutral-400">
<SearchIcon />
</div>
<input
Expand All @@ -690,7 +692,7 @@ export function SidebarSearch({
placeholder={`${placeholder} (${shortcutHint})`}
data-testid={testId}
className={cn(
'w-full rounded-lg py-2 pr-4 pl-10 text-sm',
'w-full rounded-lg py-2 ps-10 pe-4 text-sm',
'border-transparent bg-neutral-100 dark:bg-neutral-800',
'text-neutral-900 placeholder-neutral-400 dark:text-white dark:placeholder-neutral-500',
'focus:ring-primary-500 focus:bg-white focus:ring-2 focus:outline-none dark:focus:bg-neutral-700',
Expand Down
6 changes: 3 additions & 3 deletions src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(
: undefined
}
className={cn(
'text-muted-foreground h-12 px-4 text-left align-middle font-medium',
'[&:has([role=checkbox])]:pr-0',
'text-muted-foreground h-12 px-4 text-start align-middle font-medium',
'[&:has([role=checkbox])]:pe-0',
className
)}
{...props}
Expand All @@ -218,7 +218,7 @@ const TableCell = React.forwardRef<HTMLTableCellElement, TableCellProps>(
ref={ref}
data-slot="table-cell"
className={cn(
'p-4 align-middle [&:has([role=checkbox])]:pr-0',
'p-4 align-middle [&:has([role=checkbox])]:pe-0',
className
)}
{...props}
Expand Down
24 changes: 24 additions & 0 deletions src/tailwind-preset.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,30 @@ module.exports = {
'rtl:data-[state=checked]:-translate-x-4',
'rtl:data-[state=checked]:-translate-x-5',
'rtl:data-[state=checked]:-translate-x-7',
// RTL layout+nav batch: AppHeader alignment, Card accent/badge positions,
// Sheet sides, Sidebar off-canvas + paddings, Table cell alignment
'me-auto',
'ms-auto',
'-end-1',
'start-0',
'end-0',
'ps-4',
'start-2',
'end-2',
'border-e',
'border-s',
'me-3',
'ms-2',
'ps-2',
'-me-2',
'-end-3',
'start-3',
'pe-4',
'ps-10',
'rtl:translate-x-full',
'translate-x-0',
'-translate-x-full',
'[&:has([role=checkbox])]:pe-0',
// MediaEditor media-surface height cap: the small-screen `dvh` cap, and the
// md variants that hand desktop back its original percentage cap. Purging
// these uncaps the player on a phone, which is the bug the cap exists for.
Expand Down
24 changes: 24 additions & 0 deletions src/tailwind-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,30 @@ export const miewebUISafelist = [
'rtl:data-[state=checked]:-translate-x-4',
'rtl:data-[state=checked]:-translate-x-5',
'rtl:data-[state=checked]:-translate-x-7',
// RTL layout+nav batch: AppHeader alignment, Card accent/badge positions,
// Sheet sides, Sidebar off-canvas + paddings, Table cell alignment
'me-auto',
'ms-auto',
'-end-1',
'start-0',
'end-0',
'ps-4',
'start-2',
'end-2',
'border-e',
'border-s',
'me-3',
'ms-2',
'ps-2',
'-me-2',
'-end-3',
'start-3',
'pe-4',
'ps-10',
'rtl:translate-x-full',
'translate-x-0',
'-translate-x-full',
'[&:has([role=checkbox])]:pe-0',
// SchedulePicker / overflow handling
'overflow-x-auto',
'overflow-hidden',
Expand Down
Loading