Skip to content

Commit 2944299

Browse files
committed
feat!: RTL support (#179)
* refactor: replace directional classes with logical classes Replace directional classes with logical classes in custom components (excluding shadcn ui components) and pages to support RTL layouts. * refactor: update ui component classes for RTL support Update logical classes in ShadcnUI components to ensure proper RTL (Right-to-Left) support. * feat: add direction switch component and context for RTL * docs: add customized components section in README
1 parent 70cfd30 commit 2944299

60 files changed

Lines changed: 281 additions & 148 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,46 @@ I've been creating dashboard UIs at work and for my personal projects. I always
1414
- Responsive
1515
- Accessible
1616
- With built-in Sidebar component
17-
- Global Search Command
17+
- Global search command
1818
- 10+ pages
1919
- Extra custom components
20+
- RTL support
21+
22+
<details>
23+
<summary>Customized Components (click to expand)</summary>
24+
25+
This project uses Shadcn UI components, but some have been slightly modified for better RTL (Right-to-Left) support and other improvements. These customized components differ from the original Shadcn UI versions.
26+
27+
If you want to update components using the Shadcn CLI (e.g., `npx shadcn@latest add <component>`), it's generally safe for non-customized components. For the listed customized ones, you may need to manually merge changes to preserve the project's modifications and avoid overwriting RTL support or other updates.
28+
29+
> If you don't require RTL support, you can safely update the 'RTL Updated Components' via the Shadcn CLI, as these changes are primarily for RTL compatibility. The 'Modified Components' may have other customizations to consider.
30+
31+
### Modified Components
32+
33+
- scroll-area
34+
- sonner
35+
36+
### RTL Updated Components
37+
38+
- alert-dialog
39+
- calendar
40+
- command
41+
- dialog
42+
- dropdown-menu
43+
- select
44+
- table
45+
- sheet
46+
- sidebar
47+
- switch
48+
49+
**Notes:**
50+
51+
- **Modified Components**: These have general updates, potentially including RTL adjustments.
52+
- **RTL Updated Components**: These have specific changes for RTL language support (e.g., layout, positioning).
53+
- For implementation details, check the source files in `src/components/ui/`.
54+
- All other Shadcn UI components in the project are standard and can be safely updated via the CLI.
55+
56+
</details>
2057

2158
## Tech Stack
2259

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@radix-ui/react-checkbox": "^1.3.2",
2121
"@radix-ui/react-collapsible": "^1.1.11",
2222
"@radix-ui/react-dialog": "^1.1.14",
23+
"@radix-ui/react-direction": "^1.1.1",
2324
"@radix-ui/react-dropdown-menu": "^2.1.15",
2425
"@radix-ui/react-icons": "^1.3.2",
2526
"@radix-ui/react-label": "^2.1.7",

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/command-menu.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function CommandMenu() {
3838
<CommandDialog modal open={open} onOpenChange={setOpen}>
3939
<CommandInput placeholder='Type a command or search...' />
4040
<CommandList>
41-
<ScrollArea type='hover' className='h-72 pr-1'>
41+
<ScrollArea type='hover' className='h-72 pe-1'>
4242
<CommandEmpty>No results found.</CommandEmpty>
4343
{sidebarData.navGroups.map((group) => (
4444
<CommandGroup key={group.title} heading={group.title}>
@@ -52,7 +52,7 @@ export function CommandMenu() {
5252
runCommand(() => navigate({ to: navItem.url }))
5353
}}
5454
>
55-
<div className='mr-2 flex h-4 w-4 items-center justify-center'>
55+
<div className='me-2 flex h-4 w-4 items-center justify-center'>
5656
<IconArrowRightDashed className='text-muted-foreground/80 size-2' />
5757
</div>
5858
{navItem.title}
@@ -67,7 +67,7 @@ export function CommandMenu() {
6767
runCommand(() => navigate({ to: subItem.url }))
6868
}}
6969
>
70-
<div className='mr-2 flex h-4 w-4 items-center justify-center'>
70+
<div className='me-2 flex h-4 w-4 items-center justify-center'>
7171
<IconArrowRightDashed className='text-muted-foreground/80 size-2' />
7272
</div>
7373
{navItem.title} <IconChevronRight /> {subItem.title}

src/components/confirm-dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function ConfirmDialog(props: ConfirmDialogProps) {
4242
return (
4343
<AlertDialog {...actions}>
4444
<AlertDialogContent className={cn(className && className)}>
45-
<AlertDialogHeader className='text-left'>
45+
<AlertDialogHeader className='text-start'>
4646
<AlertDialogTitle>{title}</AlertDialogTitle>
4747
<AlertDialogDescription asChild>
4848
<div>{desc}</div>

src/components/date-picker.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ export function DatePicker({
2525
<Button
2626
variant='outline'
2727
data-empty={!selected}
28-
className='data-[empty=true]:text-muted-foreground w-[240px] justify-start text-left font-normal'
28+
className='data-[empty=true]:text-muted-foreground w-[240px] justify-start text-start font-normal'
2929
>
3030
{selected ? (
3131
format(selected, 'MMM d, yyyy')
3232
) : (
3333
<span>{placeholder}</span>
3434
)}
35-
<CalendarIcon className='ml-auto h-4 w-4 opacity-50' />
35+
<CalendarIcon className='ms-auto h-4 w-4 opacity-50' />
3636
</Button>
3737
</PopoverTrigger>
3838
<PopoverContent className='w-auto p-0'>

src/components/dir-switch.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { IconCheck } from '@tabler/icons-react'
2+
import { PilcrowLeftIcon, PilcrowRightIcon } from 'lucide-react'
3+
import { cn } from '@/lib/utils'
4+
import { useDirection } from '@/context/direction-context'
5+
import { Button } from '@/components/ui/button'
6+
import {
7+
DropdownMenu,
8+
DropdownMenuContent,
9+
DropdownMenuItem,
10+
DropdownMenuTrigger,
11+
} from '@/components/ui/dropdown-menu'
12+
13+
export function DirSwitch() {
14+
const { dir, setDir } = useDirection()
15+
16+
return (
17+
<DropdownMenu modal={false}>
18+
<DropdownMenuTrigger asChild>
19+
<Button variant='ghost' size='icon' className='scale-95 rounded-full'>
20+
<PilcrowLeftIcon className='size-[1.2rem] scale-100 rotate-0 transition-all rtl:scale-0 rtl:-rotate-90' />
21+
<PilcrowRightIcon className='absolute size-[1.2rem] scale-0 rotate-90 transition-all rtl:scale-100 rtl:rotate-0' />
22+
<span className='sr-only'>Toggle Site Direction</span>
23+
</Button>
24+
</DropdownMenuTrigger>
25+
<DropdownMenuContent align='end'>
26+
<DropdownMenuItem onClick={() => setDir('ltr')}>
27+
Left to Right (LTR)
28+
<IconCheck
29+
size={14}
30+
className={cn('ms-auto', dir !== 'ltr' && 'hidden')}
31+
/>
32+
</DropdownMenuItem>
33+
<DropdownMenuItem onClick={() => setDir('rtl')}>
34+
Right to Left (RTL)
35+
<IconCheck
36+
size={14}
37+
className={cn('ms-auto', dir !== 'rtl' && 'hidden')}
38+
/>
39+
</DropdownMenuItem>
40+
</DropdownMenuContent>
41+
</DropdownMenu>
42+
)
43+
}

src/components/layout/authenticated-layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function AuthenticatedLayout({ children }: Props) {
2020
<div
2121
id='content'
2222
className={cn(
23-
'ml-auto w-full max-w-full',
23+
'ms-auto w-full max-w-full',
2424
'peer-data-[state=collapsed]:w-[calc(100%-var(--sidebar-width-icon)-1rem)]',
2525
'peer-data-[state=expanded]:w-[calc(100%-var(--sidebar-width))]',
2626
'sm:transition-[width] sm:duration-200 sm:ease-linear',

src/components/layout/nav-group.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ const SidebarMenuCollapsible = ({
9696
{item.icon && <item.icon />}
9797
<span>{item.title}</span>
9898
{item.badge && <NavBadge>{item.badge}</NavBadge>}
99-
<ChevronRight className='ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
99+
<ChevronRight className='ms-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
100100
</SidebarMenuButton>
101101
</CollapsibleTrigger>
102102
<CollapsibleContent className='CollapsibleContent'>
@@ -140,7 +140,7 @@ const SidebarMenuCollapsedDropdown = ({
140140
{item.icon && <item.icon />}
141141
<span>{item.title}</span>
142142
{item.badge && <NavBadge>{item.badge}</NavBadge>}
143-
<ChevronRight className='ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
143+
<ChevronRight className='ms-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90' />
144144
</SidebarMenuButton>
145145
</DropdownMenuTrigger>
146146
<DropdownMenuContent side='right' align='start' sideOffset={4}>
@@ -157,7 +157,7 @@ const SidebarMenuCollapsedDropdown = ({
157157
{sub.icon && <sub.icon />}
158158
<span className='max-w-52 text-wrap'>{sub.title}</span>
159159
{sub.badge && (
160-
<span className='ml-auto text-xs'>{sub.badge}</span>
160+
<span className='ms-auto text-xs'>{sub.badge}</span>
161161
)}
162162
</Link>
163163
</DropdownMenuItem>

src/components/layout/nav-user.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ export function NavUser({
4848
<AvatarImage src={user.avatar} alt={user.name} />
4949
<AvatarFallback className='rounded-lg'>SN</AvatarFallback>
5050
</Avatar>
51-
<div className='grid flex-1 text-left text-sm leading-tight'>
51+
<div className='grid flex-1 text-start text-sm leading-tight'>
5252
<span className='truncate font-semibold'>{user.name}</span>
5353
<span className='truncate text-xs'>{user.email}</span>
5454
</div>
55-
<ChevronsUpDown className='ml-auto size-4' />
55+
<ChevronsUpDown className='ms-auto size-4' />
5656
</SidebarMenuButton>
5757
</DropdownMenuTrigger>
5858
<DropdownMenuContent
@@ -62,12 +62,12 @@ export function NavUser({
6262
sideOffset={4}
6363
>
6464
<DropdownMenuLabel className='p-0 font-normal'>
65-
<div className='flex items-center gap-2 px-1 py-1.5 text-left text-sm'>
65+
<div className='flex items-center gap-2 px-1 py-1.5 text-start text-sm'>
6666
<Avatar className='h-8 w-8 rounded-lg'>
6767
<AvatarImage src={user.avatar} alt={user.name} />
6868
<AvatarFallback className='rounded-lg'>SN</AvatarFallback>
6969
</Avatar>
70-
<div className='grid flex-1 text-left text-sm leading-tight'>
70+
<div className='grid flex-1 text-start text-sm leading-tight'>
7171
<span className='truncate font-semibold'>{user.name}</span>
7272
<span className='truncate text-xs'>{user.email}</span>
7373
</div>

0 commit comments

Comments
 (0)