Skip to content
This repository has been archived by the owner on Dec 11, 2024. It is now read-only.

feat: right to left layout #101

Merged
merged 17 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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
37 changes: 25 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"react-i18next": "12.2.0",
"react-refresh": "0.10.0",
"tailwindcss": "3.2.7",
"tailwindcss-rtl": "^0.9.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this library might not be the best supported, but it does what it does well. I think it is easier to read and write me-4 than ltr:ml-4 rtl:mr-4, especially when we will have that kind of thing all throughout the codebase. The start-[x] and end-[x] utilities are also very useful, instead of ltr:left-[x] rtl:right-[x].

The only bug I really ran into was that the library doesn't handle negative numbers. 20lives/tailwindcss-rtl#53. It does look like the maintainer has quite releasing updates.

Copy link
Member

@arrocke arrocke Jun 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in total agreement about the ease of these new utilities. However, given that we've already run into a limitation of this unmaintained library, what do you think about using the code from the library directly in our tailwind config? Since we are going to be heavily using direction utilities, maybe we should have control over them. Here is all that it would take to add margin utilities, including negative margins. That just wasn't released in this library. We wouldn't need to add everything the library supports to start, just the ones that we need.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't even think of this. Good idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I brought over all the utilities we are using.

"ts-node": "10.9.1",
"typescript": "4.9.5",
"url-loader": "4.1.1",
Expand Down
10 changes: 5 additions & 5 deletions packages/web/src/app/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export default function Header({ language, onLanguageChange }: HeaderProps) {
);

return (
<header className="p-2 flex items-baseline z-10">
<header className="p-2 flex items-baseline flex-row z-10">
<h1 className="font-bold text-lg">Gloss Translation</h1>
<div className="flex-grow" />
<nav className="flex items-baseline" aria-label="primary">
{translationLanguages.length > 0 && (
<DropdownMenu
text={selectedLanguage?.name ?? 'Language'}
className="mr-4"
className="me-4"
>
<DropdownMenuSubmenu text={t('switch_language')}>
{translationLanguages.map((language) => (
Expand All @@ -59,23 +59,23 @@ export default function Header({ language, onLanguageChange }: HeaderProps) {
{session.status === 'authenticated' && (
<DropdownMenu text={session.user.name ?? ''}>
<DropdownMenuLink to={'#'}>
<Icon icon="user" className="mr-2" fixedWidth />
<Icon icon="user" className="me-2" fixedWidth />
Profile
</DropdownMenuLink>
<DropdownMenuButton
onClick={() => {
languageDialog.current?.open();
}}
>
<Icon icon="earth" className="mr-2" fixedWidth />
<Icon icon="earth" className="me-2" fixedWidth />
{(interfaceLanguages as { [code: string]: string })[
i18n.resolvedLanguage
] ?? t('language', { count: 100 })}
</DropdownMenuButton>
<DropdownMenuLink
to={`${process.env.API_URL}/api/auth/signout?callbackUrl=${window.location.href}`}
>
<Icon icon="right-from-bracket" className="mr-2" fixedWidth />
<Icon icon="right-from-bracket" className="me-2" fixedWidth />
{t('log_out')}
</DropdownMenuLink>
</DropdownMenu>
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/app/LanguageDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ const LanguageDialog = forwardRef<DialogRef>((_, ref) => {
const { i18n } = useTranslation();

return (
<Dialog ref={ref} className="fixed bottom-4 right-4 m-0">
<Dialog ref={ref} className="fixed bottom-4 end-4 m-0">
<h1 className="text-lg mb-4">
<Icon icon="earth" className="mr-2" />
<Icon icon="earth" className="me-2" />
Language
</h1>

Expand Down
5 changes: 3 additions & 2 deletions packages/web/src/app/languages.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"en": "English",
"es": "Español"
}
"es": "Español",
"ar": "اَلْعَرَبِيَّةُ"
}
2 changes: 1 addition & 1 deletion packages/web/src/features/languages/LanguagesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function LanguagesView() {
{session.user?.systemRoles.includes(SystemRole.Admin) && (
<ListRowAction colSpan={2}>
<Link to="./new">
<Icon icon="plus" className="mr-1" />
<Icon icon="plus" className="me-1" />
{t('add_language')}
</Link>
</ListRowAction>
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/features/languages/ManageLanguageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function ManageLanguageView() {
</div>
<div>
<Button type="submit">{t('update')}</Button>
<SubmittingIndicator className="ml-3" />
<SubmittingIndicator className="ms-3" />
</div>
</Form>
</Card>
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/features/languages/NewLanguageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default function NewLanguageView() {
</div>
<div>
<Button type="submit">{t('create')}</Button>
<SubmittingIndicator className="ml-3" />
<SubmittingIndicator className="ms-3" />
</div>
</Form>
</Card>
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/features/translation/TranslateWord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ export default function TranslateWord({
if (status === 'saving') {
return (
<>
<Icon icon="arrows-rotate" className="mr-1" />
<Icon icon="arrows-rotate" className="me-1" />
{capitalize(t('saving'))}
</>
);
} else if (status === 'saved') {
return (
<>
<Icon icon="check" className="mr-1" />
<Icon icon="check" className="me-1" />
{capitalize(t('saved'))}
</>
);
Expand Down
24 changes: 14 additions & 10 deletions packages/web/src/features/translation/VerseSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface VerseSelectorProps {
}

export function VerseSelector({ verseId, onVerseChange }: VerseSelectorProps) {
const { t } = useTranslation(['translation', 'bible']);
const { t, i18n } = useTranslation(['translation', 'bible']);
const verseInfo = parseVerseId(verseId);

const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
Expand All @@ -34,22 +34,26 @@ export function VerseSelector({ verseId, onVerseChange }: VerseSelectorProps) {
};

return (
<div className="flex flex-row gap-4 items-center">
<div className="flex gap-4 items-center flex-row">
<TextInput
name="verseReference"
autoComplete="off"
placeholder={generateReference(verseInfo, t)}
onKeyDown={onKeyDown}
arial-label={t('select_verse')}
/>
<button onClick={() => onVerseChange(decrementVerseId(verseId))}>
<Icon icon="arrow-left" />
<span className="sr-only">{t('previous_verse')}</span>
</button>
<button onClick={() => onVerseChange(incrementVerseId(verseId))}>
<Icon icon="arrow-right" />
<span className="sr-only">{t('next_verse')}</span>
</button>
{/* Use flex-row-reverse on rtl, so that the previous button is always
to the left of the next button. */}
<div className="flex ltr:flex-row rtl:flex-row-reverse gap-4">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a RTL language, we want the next button to be on the right. It's confusing for us, but natural in those languages. We should be able to remove this secondary flex box and leave it in the natural rtl order, but flip the icons.

Might be easiest to render two icons and show/hide the correct one with tailwind rather than trying to manage that with react

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered if this was the case or not

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just looked so strange to me 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

<button onClick={() => onVerseChange(decrementVerseId(verseId))}>
<Icon icon="arrow-left" />
<span className="sr-only">{t('previous_verse')}</span>
</button>
<button onClick={() => onVerseChange(incrementVerseId(verseId))}>
<Icon icon="arrow-right" />
<span className="sr-only">{t('next_verse')}</span>
</button>
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion packages/web/src/features/users/InviteUserView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default function InviteUserView() {
</div>
<div>
<Button type="submit">{t('invite')}</Button>
<SubmittingIndicator className="ml-3" />
<SubmittingIndicator className="ms-3" />
</div>
</Form>
</Card>
Expand Down
29 changes: 18 additions & 11 deletions packages/web/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom/client';
import { useTranslation } from 'react-i18next';
import { RouterProvider } from 'react-router-dom';
import router from './app/router';
import './app/i18n';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import router from './app/router';
import { FlashProvider } from './shared/hooks/flash';

function App() {
const { i18n } = useTranslation();
document.body.dir = i18n.dir();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only update this when the language changes. We can use useEffect to set up event listeners to i18next events

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I should create a local "dir" variable, update it in the event callback, and watch that variable in the useEffect? (I'm still trying to get comfortable with the react way of doing things)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this should work:

useEffect(() => {
  function handler() {
    document.body.dir = i18next.dir()
  }
  i18next.on('initialized', handler)
  i18next.on('languageChanged', handler)
}, [i18next.dir, i18next.on])

That will attach event listeners to i18next and only apply changes when the language changes. Both the dir and on functions should be stable (meaning their reference doesn't change), so this effect will fire only once on first render and attach the listeners.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this, but the handler never got called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that both events trigger before the callback of useEffect is called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my newest commit fixes it. Instead of using the initialized event, I just call the handler directly from the effect.

return (
<StrictMode>
<QueryClientProvider client={queryClient}>
<FlashProvider>
<RouterProvider router={router} />
</FlashProvider>
</QueryClientProvider>
</StrictMode>
);
}

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);

const queryClient = new QueryClient();

root.render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<FlashProvider>
<RouterProvider router={router} />
</FlashProvider>
</QueryClientProvider>
</StrictMode>
);
root.render(<App />);
Loading