mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-24 08:54:05 +10:00
Improved the Right to Left Direction implementation for relevant languages. (#2583)
* Added support for right to left direction of bullet points in the page render and text editor * Added right to left direction support by adding a toggle button in the layout and enhancing the css element of the page render to support it without affecting the default left to right direction * Moved the Right to Life Direction Toggle feature to be integrated with the Languages section using locale * change all occurrances of pl, pr, ml, mr, left, right to start/end equivalents --------- Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
This commit is contained in:
@@ -79,7 +79,7 @@ function RouteComponent() {
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
disabled={isPrinting}
|
||||
className="fixed right-4 bottom-4 z-50 hidden rounded-full px-4 md:inline-flex print:hidden"
|
||||
className="fixed end-4 bottom-4 z-50 hidden rounded-full px-4 md:inline-flex print:hidden"
|
||||
onClick={handleDownload}
|
||||
>
|
||||
{isPrinting ? <Spinner /> : <DownloadSimpleIcon />}
|
||||
|
||||
@@ -131,8 +131,8 @@ export const DonationBanner = () => (
|
||||
<FloatingIcon icon={HeartIcon} className="bottom-[20%] left-[20%]" delay={2.5} />
|
||||
|
||||
{/* Gradient Orbs */}
|
||||
<div className="absolute top-1/4 -left-32 size-64 rounded-full bg-primary/5 blur-3xl" />
|
||||
<div className="absolute -right-32 bottom-1/4 size-64 rounded-full bg-rose-500/5 blur-3xl" />
|
||||
<div className="absolute -start-32 top-1/4 size-64 rounded-full bg-primary/5 blur-3xl" />
|
||||
<div className="absolute -end-32 bottom-1/4 size-64 rounded-full bg-rose-500/5 blur-3xl" />
|
||||
</div>
|
||||
|
||||
<div className="container relative px-8">
|
||||
@@ -146,8 +146,8 @@ export const DonationBanner = () => (
|
||||
>
|
||||
<div aria-hidden="true" className="relative mb-6">
|
||||
<PulsingHeart />
|
||||
<SparkleEffect className="-top-2 -right-4" />
|
||||
<SparkleEffect className="bottom-0 -left-3" />
|
||||
<SparkleEffect className="-end-4 -top-2" />
|
||||
<SparkleEffect className="-start-3 bottom-0" />
|
||||
</div>
|
||||
|
||||
<motion.h2
|
||||
|
||||
@@ -66,11 +66,11 @@ export function FAQ() {
|
||||
return (
|
||||
<section
|
||||
id="frequently-asked-questions"
|
||||
className="flex flex-col gap-x-16 gap-y-6 p-4 md:p-8 lg:flex-row lg:gap-x-24 xl:py-16"
|
||||
className="flex flex-col gap-x-16 gap-y-6 p-4 md:p-8 lg:flex-row lg:gap-x-18 xl:py-16"
|
||||
>
|
||||
<motion.h2
|
||||
className={cn(
|
||||
"font-semibold text-2xl tracking-tight md:text-4xl xl:text-5xl",
|
||||
"flex-1 font-semibold text-2xl tracking-tight md:text-4xl xl:text-5xl",
|
||||
"flex shrink-0 flex-wrap items-center gap-x-1.5 lg:flex-col lg:items-start",
|
||||
)}
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
@@ -86,7 +86,7 @@ export function FAQ() {
|
||||
</motion.h2>
|
||||
|
||||
<motion.div
|
||||
className="max-w-2xl flex-1 lg:ml-auto 2xl:max-w-3xl"
|
||||
className="max-w-2xl flex-2 lg:ml-auto 2xl:max-w-3xl"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
|
||||
@@ -134,7 +134,7 @@ function FooterLink({ url, label }: FooterLinkItem) {
|
||||
initial={{ width: 0, opacity: 0 }}
|
||||
animate={isHovered ? { width: "100%", opacity: 1 } : { width: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.25, ease: "easeOut" }}
|
||||
className="pointer-events-none absolute -bottom-0.5 left-0 h-px rounded bg-primary"
|
||||
className="pointer-events-none absolute start-0 -bottom-0.5 h-px rounded bg-primary"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -119,7 +119,7 @@ export function Hero() {
|
||||
<motion.div
|
||||
aria-hidden="true"
|
||||
role="presentation"
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2"
|
||||
className="absolute start-1/2 bottom-8 -translate-x-1/2"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 2, duration: 1 }}
|
||||
|
||||
@@ -7,8 +7,8 @@ export function Prefooter() {
|
||||
<section id="prefooter" className="relative overflow-hidden py-16 md:py-24">
|
||||
{/* Background decoration */}
|
||||
<div aria-hidden="true" className="pointer-events-none absolute inset-0">
|
||||
<div className="absolute top-0 left-1/4 size-96 rounded-full bg-primary/5 blur-3xl" />
|
||||
<div className="absolute right-1/4 bottom-0 size-96 rounded-full bg-primary/5 blur-3xl" />
|
||||
<div className="absolute start-1/4 top-0 size-96 rounded-full bg-primary/5 blur-3xl" />
|
||||
<div className="absolute end-1/4 bottom-0 size-96 rounded-full bg-primary/5 blur-3xl" />
|
||||
</div>
|
||||
|
||||
<div className="relative space-y-8">
|
||||
|
||||
@@ -33,7 +33,7 @@ export function ProductHuntBanner() {
|
||||
href="https://www.producthunt.com/products/reactive-resume/launches/reactive-resume-v5-2?embed=true&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-reactive-resume-v5-2"
|
||||
>
|
||||
<Trans>Reactive Resume is launching on Product Hunt today, head over to show some love!</Trans>
|
||||
<HeartIcon weight="fill" color="#DA552F" className="ml-2 size-3.5" />
|
||||
<HeartIcon weight="fill" color="#DA552F" className="ms-2 size-3.5" />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ function StatisticCard({ statistic, index }: StatisticCardProps) {
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="group relative flex flex-col items-center justify-center gap-y-4 border-r border-b p-8 transition-colors last:border-r-0 hover:bg-secondary/30 sm:border-b-0 xl:py-12"
|
||||
className="group relative flex flex-col items-center justify-center gap-y-4 border-r border-b p-8 transition-colors last:border-e-0 hover:bg-secondary/30 sm:border-b-0 xl:py-12"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
@@ -47,7 +47,7 @@ function StatisticCard({ statistic, index }: StatisticCardProps) {
|
||||
{/* Background decoration */}
|
||||
<div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
|
||||
<motion.div
|
||||
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-primary/2"
|
||||
className="absolute start-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-primary/2"
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
whileInView={{ scale: 1, opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
|
||||
@@ -23,7 +23,7 @@ function TemplateItem({ metadata }: TemplateItemProps) {
|
||||
<div className="absolute inset-0 bg-linear-to-t from-black/70 via-black/20 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
||||
|
||||
{/* Template name on hover */}
|
||||
<div className="absolute right-0 bottom-0 left-0 translate-y-full p-4 transition-transform duration-300 group-hover:translate-y-0">
|
||||
<div className="absolute start-0 end-0 bottom-0 translate-y-full p-4 transition-transform duration-300 group-hover:translate-y-0">
|
||||
<p className="font-semibold text-white drop-shadow-lg">{metadata.name}</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -115,10 +115,10 @@ export function Testimonials() {
|
||||
|
||||
<div className="relative">
|
||||
{/* Left fade */}
|
||||
<div className="pointer-events-none absolute top-0 bottom-0 left-0 z-10 w-16 bg-linear-to-r from-background to-transparent sm:w-24 md:w-32 lg:w-48" />
|
||||
<div className="pointer-events-none absolute start-0 top-0 bottom-0 z-10 w-16 bg-linear-to-r from-background to-transparent sm:w-24 md:w-32 lg:w-48" />
|
||||
|
||||
{/* Right fade */}
|
||||
<div className="pointer-events-none absolute top-0 right-0 bottom-0 z-10 w-16 bg-linear-to-l from-background to-transparent sm:w-24 md:w-32 lg:w-48" />
|
||||
<div className="pointer-events-none absolute end-0 top-0 bottom-0 z-10 w-16 bg-linear-to-l from-background to-transparent sm:w-24 md:w-32 lg:w-48" />
|
||||
|
||||
<div className="flex flex-col gap-y-6">
|
||||
<MarqueeRow testimonials={row1} rowId="row1" direction="left" duration={50} />
|
||||
|
||||
@@ -11,7 +11,7 @@ function RouteComponent() {
|
||||
<>
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-100 focus:rounded-md focus:bg-background focus:px-4 focus:py-2 focus:text-foreground focus:shadow-lg focus:ring-2 focus:ring-ring"
|
||||
className="sr-only focus:not-sr-only focus:fixed focus:start-4 focus:top-4 focus:z-100 focus:rounded-md focus:bg-background focus:px-4 focus:py-2 focus:text-foreground focus:shadow-lg focus:ring-2 focus:ring-ring"
|
||||
>
|
||||
<Trans>Skip to main content</Trans>
|
||||
</a>
|
||||
|
||||
@@ -10,7 +10,7 @@ export function BuilderSidebarEdge({ side, children }: Props) {
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-y-0 hidden w-12 flex-col items-center justify-between bg-popover py-2.5 sm:flex",
|
||||
side === "left" ? "left-0 border-r" : "right-0 border-l",
|
||||
side === "left" ? "start-0 border-r" : "end-0 border-l",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -44,9 +44,9 @@ export function BuilderHeader() {
|
||||
<HouseSimpleIcon />
|
||||
</Link>
|
||||
</Button>
|
||||
<span className="mr-2.5 text-muted-foreground">/</span>
|
||||
<span className="me-2.5 text-muted-foreground">/</span>
|
||||
<h2 className="flex-1 truncate font-medium">{name}</h2>
|
||||
{isLocked && <LockSimpleIcon className="ml-2 text-muted-foreground" />}
|
||||
{isLocked && <LockSimpleIcon className="ms-2 text-muted-foreground" />}
|
||||
<BuilderHeaderDropdown />
|
||||
</div>
|
||||
|
||||
@@ -131,24 +131,24 @@ function BuilderHeaderDropdown() {
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem disabled={isLocked} onSelect={handleUpdate}>
|
||||
<PencilSimpleLineIcon className="mr-2" />
|
||||
<PencilSimpleLineIcon className="me-2" />
|
||||
<Trans>Update</Trans>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onSelect={handleDuplicate}>
|
||||
<CopySimpleIcon className="mr-2" />
|
||||
<CopySimpleIcon className="me-2" />
|
||||
<Trans>Duplicate</Trans>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onSelect={handleToggleLock}>
|
||||
{isLocked ? <LockSimpleOpenIcon className="mr-2" /> : <LockSimpleIcon className="mr-2" />}
|
||||
{isLocked ? <LockSimpleOpenIcon className="me-2" /> : <LockSimpleIcon className="me-2" />}
|
||||
{isLocked ? <Trans>Unlock</Trans> : <Trans>Lock</Trans>}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem variant="destructive" disabled={isLocked} onSelect={handleDelete}>
|
||||
<TrashSimpleIcon className="mr-2" />
|
||||
<TrashSimpleIcon className="me-2" />
|
||||
<Trans>Delete</Trans>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -54,7 +54,7 @@ export function BuilderSidebarLeft() {
|
||||
<>
|
||||
<SidebarEdge scrollAreaRef={scrollAreaRef} />
|
||||
|
||||
<ScrollArea ref={scrollAreaRef} className="@container h-[calc(100svh-3.5rem)] bg-background sm:ml-12">
|
||||
<ScrollArea ref={scrollAreaRef} className="@container h-[calc(100svh-3.5rem)] bg-background sm:ms-12">
|
||||
<div className="space-y-4 p-4">
|
||||
{leftSidebarSections.map((section) => (
|
||||
<Fragment key={section}>
|
||||
|
||||
@@ -58,7 +58,7 @@ export function CustomFieldsSection({ onSubmit }: Props) {
|
||||
<FormControl>
|
||||
<IconPicker
|
||||
{...field}
|
||||
className="rounded-r-none! border-r-0!"
|
||||
className="rounded-r-none! border-e-0!"
|
||||
onChange={(icon) => {
|
||||
field.onChange(icon);
|
||||
form.handleSubmit(onSubmit)();
|
||||
@@ -90,7 +90,7 @@ export function CustomFieldsSection({ onSubmit }: Props) {
|
||||
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button size="icon" variant="ghost" className="ml-1">
|
||||
<Button size="icon" variant="ghost" className="ms-1">
|
||||
<LinkIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@@ -155,7 +155,7 @@ function CustomFieldItem({ field, children }: CustomFieldItemProps) {
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="mr-2 touch-none"
|
||||
className="me-2 touch-none"
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault();
|
||||
controls.start(e);
|
||||
|
||||
@@ -36,7 +36,7 @@ export function SectionBase({ type, className, ...props }: Props) {
|
||||
>
|
||||
<AccordionItem value={type} className="group/accordion space-y-4">
|
||||
<div className="flex items-center">
|
||||
<AccordionTrigger asChild className="mr-2 items-center justify-center">
|
||||
<AccordionTrigger asChild className="me-2 items-center justify-center">
|
||||
<Button size="icon" variant="ghost">
|
||||
<CaretRightIcon />
|
||||
</Button>
|
||||
|
||||
@@ -244,7 +244,7 @@ export function SectionItem<T extends SectionItemType>({ type, item, title, subt
|
||||
<button
|
||||
onClick={onUpdate}
|
||||
className={cn(
|
||||
"flex flex-1 flex-col items-start justify-center space-y-0.5 pl-1.5 text-start opacity-100 transition-opacity hover:bg-secondary/40 focus:outline-none focus-visible:ring-1",
|
||||
"flex flex-1 flex-col items-start justify-center space-y-0.5 ps-1.5 text-start opacity-100 transition-opacity hover:bg-secondary/40 focus:outline-none focus-visible:ring-1",
|
||||
item.hidden && "opacity-50",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -47,7 +47,7 @@ export function BuilderSidebarRight() {
|
||||
<>
|
||||
<SidebarEdge scrollAreaRef={scrollAreaRef} />
|
||||
|
||||
<ScrollArea ref={scrollAreaRef} className="@container h-[calc(100svh-3.5rem)] bg-background sm:mr-12">
|
||||
<ScrollArea ref={scrollAreaRef} className="@container h-[calc(100svh-3.5rem)] bg-background sm:me-12">
|
||||
<div className="space-y-4 p-4">
|
||||
{rightSidebarSections.map((section) => (
|
||||
<Fragment key={section}>
|
||||
|
||||
@@ -24,13 +24,15 @@ const formSchema = metadataSchema.shape.layout.omit({ pages: true });
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
function LayoutSectionForm() {
|
||||
const sidebarWidth = useResumeStore((state) => state.resume.data.metadata.layout.sidebarWidth);
|
||||
const layout = useResumeStore((state) => state.resume.data.metadata.layout);
|
||||
const updateResumeData = useResumeStore((state) => state.updateResumeData);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
mode: "onChange",
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: { sidebarWidth },
|
||||
defaultValues: {
|
||||
sidebarWidth: layout.sidebarWidth,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (data: FormValues) => {
|
||||
|
||||
@@ -293,7 +293,7 @@ function LayoutColumn({ pageIndex, columnId, items, disabled = false }: LayoutCo
|
||||
return (
|
||||
<SortableContext id={droppableId} items={items} strategy={verticalListSortingStrategy}>
|
||||
<div className={cn("space-y-1.5", disabled && "opacity-50")}>
|
||||
<div className="@md:row-start-1 pl-4 font-medium text-xs">{getColumnLabel(columnId)}</div>
|
||||
<div className="@md:row-start-1 ps-4 font-medium text-xs">{getColumnLabel(columnId)}</div>
|
||||
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
|
||||
@@ -24,7 +24,7 @@ export function SectionBase({ type, className, ...props }: Props) {
|
||||
>
|
||||
<AccordionItem value={type} className="group/accordion space-y-4">
|
||||
<div className="flex items-center">
|
||||
<AccordionTrigger asChild className="mr-2 items-center justify-center">
|
||||
<AccordionTrigger asChild className="me-2 items-center justify-center">
|
||||
<Button size="icon" variant="ghost">
|
||||
<CaretRightIcon />
|
||||
</Button>
|
||||
|
||||
@@ -11,7 +11,7 @@ type Props = {
|
||||
export function DashboardHeader({ title, icon: IconComponent, className }: Props) {
|
||||
return (
|
||||
<div className={cn("relative flex items-center justify-center gap-x-2.5 md:justify-start", className)}>
|
||||
<SidebarTrigger className="absolute left-0 md:hidden" />
|
||||
<SidebarTrigger className="absolute start-0 md:hidden" />
|
||||
<IconComponent weight="light" className="size-5" />
|
||||
<h1 className="font-medium text-xl tracking-tight">{title}</h1>
|
||||
</div>
|
||||
|
||||
@@ -95,7 +95,7 @@ function SidebarItemList({ items }: SidebarItemListProps) {
|
||||
<SidebarMenuButton asChild title={i18n.t(item.label)}>
|
||||
<Link to={item.href} activeProps={{ className: "bg-sidebar-accent" }}>
|
||||
{item.icon}
|
||||
<span className="shrink-0 transition-[margin,opacity] duration-200 ease-in-out group-data-[collapsible=icon]:-ml-8 group-data-[collapsible=icon]:opacity-0">
|
||||
<span className="shrink-0 transition-[margin,opacity] duration-200 ease-in-out group-data-[collapsible=icon]:-ms-8 group-data-[collapsible=icon]:opacity-0">
|
||||
{i18n.t(item.label)}
|
||||
</span>
|
||||
</Link>
|
||||
@@ -161,7 +161,7 @@ export function DashboardSidebar() {
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div className="transition-[margin,opacity] duration-200 ease-in-out group-data-[collapsible=icon]:-ml-8 group-data-[collapsible=icon]:opacity-0">
|
||||
<div className="transition-[margin,opacity] duration-200 ease-in-out group-data-[collapsible=icon]:-ms-8 group-data-[collapsible=icon]:opacity-0">
|
||||
<p className="font-medium">{session.user.name}</p>
|
||||
<p className="text-muted-foreground text-xs">{session.user.email}</p>
|
||||
</div>
|
||||
|
||||
@@ -73,7 +73,7 @@ function RouteComponent() {
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<Combobox
|
||||
value={sort}
|
||||
options={sortOptions}
|
||||
@@ -116,7 +116,7 @@ function RouteComponent() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<Tabs className="ml-auto" value={view} onValueChange={onViewChange}>
|
||||
<Tabs className="ltr:ms-auto rtl:me-auto" value={view} onValueChange={onViewChange}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="grid" className="rounded-r-none">
|
||||
<GridFourIcon />
|
||||
|
||||
@@ -29,7 +29,7 @@ function RouteComponent() {
|
||||
<SidebarProvider open={sidebarState} onOpenChange={handleSidebarOpenChange}>
|
||||
<DashboardSidebar />
|
||||
|
||||
<main className="@container flex-1 p-4 md:pl-2">
|
||||
<main className="@container flex-1 p-4 md:ps-2">
|
||||
<Outlet />
|
||||
</main>
|
||||
</SidebarProvider>
|
||||
|
||||
@@ -104,7 +104,7 @@ function PasskeyItem({ passkey }: PasskeyItemProps) {
|
||||
<TrashSimpleIcon />
|
||||
</Button>
|
||||
|
||||
<span className="mx-2 truncate text-nowrap border-r pr-2 font-medium">{passkey.name ?? "1Password"}</span>
|
||||
<span className="mx-2 truncate text-nowrap border-r pe-2 font-medium">{passkey.name ?? "1Password"}</span>
|
||||
|
||||
<span className="flex-1 truncate text-nowrap text-muted-foreground text-xs">
|
||||
<Trans>Added on {passkey.createdAt.toLocaleDateString()}</Trans>
|
||||
|
||||
Reference in New Issue
Block a user