From 2c75e7f83d94a8629693969e312ec13ee30f3333 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matja=C5=BE=20Horvat?=
Date: Wed, 22 Nov 2023 17:16:56 +0100
Subject: [PATCH] Implement a single-column UI for smaller screens (#3024)
* Move third column under the middle column
* Add Entities list toggle
* Animate switching between the editor and the string list
* When hovering over strings in the string list, show indicator to open the editor
* Hide main navigation
* For easier development, move all @media max-width to App.css temporarily
Also:
* Remove unused flex properties
* Drop 700px transition on the header
---
translate/public/locale/en-US/translate.ftl | 2 +
translate/src/App.css | 65 ++++++++++++++++++-
translate/src/App.tsx | 10 ++-
translate/src/context/EntitiesList.tsx | 27 ++++++++
translate/src/index.tsx | 5 +-
.../entitieslist/components/Entity.css | 8 +++
.../entitieslist/components/Entity.tsx | 4 ++
.../components/EntityNavigation.css | 6 ++
.../components/EntityNavigation.tsx | 19 ++++++
translate/src/rootReducer.ts | 2 -
10 files changed, 141 insertions(+), 7 deletions(-)
create mode 100644 translate/src/context/EntitiesList.tsx
diff --git a/translate/public/locale/en-US/translate.ftl b/translate/public/locale/en-US/translate.ftl
index 75a71f149c..e3f1642c7f 100644
--- a/translate/public/locale/en-US/translate.ftl
+++ b/translate/public/locale/en-US/translate.ftl
@@ -228,6 +228,8 @@ editor-UnsavedChanges--proceed = PROCEED
## Entity Details Navigation
## Shows Copy Link and Next/Previous buttons.
+entitydetails-EntityNavigation--string-list = STRINGS
+ .title = Go to String List
entitydetails-EntityNavigation--link = COPY LINK
.title = Copy Link to String
entitydetails-EntityNavigation--next = NEXT
diff --git a/translate/src/App.css b/translate/src/App.css
index 9a91cee91e..8d54517d58 100644
--- a/translate/src/App.css
+++ b/translate/src/App.css
@@ -1,7 +1,6 @@
#app {
display: flex;
flex-direction: column;
- flex-flow: column;
height: 100%;
}
@@ -10,13 +9,11 @@
border-bottom: 1px solid var(--main-border-1);
box-sizing: border-box;
height: 60px;
- min-width: 700px;
position: relative;
}
#app > .main-content {
display: flex;
- flex: 1;
justify-content: space-between;
overflow: auto;
}
@@ -62,3 +59,65 @@
opacity: 1;
transform: rotate(3deg) translate(0px, -4px);
}
+
+@media screen and (max-width: 600px) {
+ /* Header */
+ #app > header {
+ min-width: auto;
+ }
+
+ #app .navigation > ul > li:first-child {
+ margin-right: -7px;
+ }
+
+ #app .navigation > ul > li:not(:first-child) {
+ display: none;
+ }
+
+ /* Main Content */
+ #app > .main-content {
+ overflow-x: hidden; /* Prevent horizontal scroll */
+ }
+
+ #app > .main-content > .panel-list,
+ #app > .main-content > .panel-content {
+ flex: none;
+ left: -100%;
+ transition: left 0.3s ease-in-out;
+ width: 100%;
+ }
+
+ /* String List */
+ #app > .main-content.entities-list {
+ overflow: hidden; /* Prevent double scroll */
+ }
+
+ #app > .main-content.entities-list > .panel-list,
+ #app > .main-content.entities-list > .panel-content {
+ left: 0%;
+ }
+
+ #app .entity:hover .indicator {
+ display: block;
+ }
+
+ /* Editor */
+ #app .entity-details {
+ display: block;
+ }
+
+ #app .entity-details > section {
+ height: auto;
+ max-height: none;
+ width: 100%;
+ }
+
+ #app .entity-navigation button.string-list {
+ display: block;
+ margin-right: 15px;
+ }
+
+ #app .entity-navigation button.previous {
+ margin-right: 15px;
+ }
+}
diff --git a/translate/src/App.tsx b/translate/src/App.tsx
index 21ca8edb24..1a99a86286 100644
--- a/translate/src/App.tsx
+++ b/translate/src/App.tsx
@@ -1,8 +1,10 @@
import { useLocalization } from '@fluent/react';
+import classNames from 'classnames';
import React, { useContext, useEffect, useState } from 'react';
import './App.css';
+import { EntitiesList as EntitiesListContext } from './context/EntitiesList';
import { EntityViewProvider } from '~/context/EntityView';
import { initLocale, Locale, updateLocale } from './context/Locale';
import { Location } from './context/Location';
@@ -36,6 +38,7 @@ export function App() {
const dispatch = useAppDispatch();
const location = useContext(Location);
const batchactions = useBatchactions();
+ const { visible } = useContext(EntitiesListContext);
const { l10n } = useLocalization();
const [locale, _setLocale] = useState(initLocale((next) => _setLocale(next)));
@@ -73,7 +76,12 @@ export function App() {
-
+
diff --git a/translate/src/context/EntitiesList.tsx b/translate/src/context/EntitiesList.tsx
new file mode 100644
index 0000000000..79726cf299
--- /dev/null
+++ b/translate/src/context/EntitiesList.tsx
@@ -0,0 +1,27 @@
+import { createContext, useState } from 'react';
+
+type EntitiesList = Readonly<{
+ visible: boolean;
+ show(visible: boolean): void;
+}>;
+
+const initEntitiesList: EntitiesList = {
+ visible: false,
+ show: () => {},
+};
+
+export const EntitiesList = createContext(initEntitiesList);
+
+export function EntitiesListProvider({
+ children,
+}: {
+ children: React.ReactElement;
+}) {
+ const [state, setState] = useState(() => ({
+ ...initEntitiesList,
+ show: (visible: boolean) => setState((prev) => ({ ...prev, visible })),
+ }));
+ return (
+ {children}
+ );
+}
diff --git a/translate/src/index.tsx b/translate/src/index.tsx
index e8768be16c..2b8e2c9b55 100644
--- a/translate/src/index.tsx
+++ b/translate/src/index.tsx
@@ -6,6 +6,7 @@ import { Provider } from 'react-redux';
import './index.css';
import { App } from './App';
+import { EntitiesListProvider } from './context/EntitiesList';
import { LocationProvider } from './context/Location';
import { UnsavedChangesProvider } from './context/UnsavedChanges';
import { AppLocalizationProvider } from './modules/l10n/components/AppLocalizationProvider';
@@ -22,7 +23,9 @@ render(
-
+
+
+
diff --git a/translate/src/modules/entitieslist/components/Entity.css b/translate/src/modules/entitieslist/components/Entity.css
index f92a88b5b0..c75c7c8630 100644
--- a/translate/src/modules/entitieslist/components/Entity.css
+++ b/translate/src/modules/entitieslist/components/Entity.css
@@ -54,6 +54,14 @@
font-style: italic;
}
+.entity .indicator {
+ display: none;
+ color: var(--light-grey-6);
+ position: absolute;
+ top: 26px;
+ right: 10px;
+}
+
/* Make selection area bigger and fit the entire row for easier use */
.entity > .status {
margin: -13px -13px -13px -16px;
diff --git a/translate/src/modules/entitieslist/components/Entity.tsx b/translate/src/modules/entitieslist/components/Entity.tsx
index 7d586f8280..0469defaf7 100644
--- a/translate/src/modules/entitieslist/components/Entity.tsx
+++ b/translate/src/modules/entitieslist/components/Entity.tsx
@@ -3,6 +3,7 @@ import classNames from 'classnames';
import React, { useCallback, useContext, useState } from 'react';
import type { Entity as EntityType } from '~/api/entity';
+import { EntitiesList } from '~/context/EntitiesList';
import { Locale } from '~/context/Locale';
import type { Location } from '~/context/Location';
import { useTranslationStatus } from '~/modules/entities/useTranslationStatus';
@@ -52,6 +53,7 @@ export function Entity({
}: Props): React.ReactElement<'li'> {
const isTranslator = useTranslator();
const [areSiblingsActive, setSiblingsActive] = useState(false);
+ const entitiesList = useContext(EntitiesList);
const handleSelectEntity = useCallback(
(ev: React.MouseEvent) => {
@@ -62,6 +64,7 @@ export function Entity({
)
) {
selectEntity(entity);
+ entitiesList.show(false);
}
},
[entity, selectEntity],
@@ -146,6 +149,7 @@ export function Entity({
search={parameters.search}
/>
+
);
diff --git a/translate/src/modules/entitydetails/components/EntityNavigation.css b/translate/src/modules/entitydetails/components/EntityNavigation.css
index dfcc33826c..a82385c2ca 100644
--- a/translate/src/modules/entitydetails/components/EntityNavigation.css
+++ b/translate/src/modules/entitydetails/components/EntityNavigation.css
@@ -13,6 +13,12 @@
padding: 0;
}
+.entity-navigation button.string-list {
+ display: none;
+ float: left;
+ margin-right: 30px;
+}
+
.entity-navigation button.link {
float: left;
}
diff --git a/translate/src/modules/entitydetails/components/EntityNavigation.tsx b/translate/src/modules/entitydetails/components/EntityNavigation.tsx
index 4d0c7e878a..dc90e983a5 100644
--- a/translate/src/modules/entitydetails/components/EntityNavigation.tsx
+++ b/translate/src/modules/entitydetails/components/EntityNavigation.tsx
@@ -1,6 +1,7 @@
import { Localized } from '@fluent/react';
import React, { useCallback, useContext, useEffect } from 'react';
+import { EntitiesList } from '~/context/EntitiesList';
import { Location } from '~/context/Location';
import { ShowNotification } from '~/context/Notification';
import { UnsavedActions } from '~/context/UnsavedChanges';
@@ -20,6 +21,11 @@ export function EntityNavigation(): React.ReactElement {
const nextEntity = useNextEntity();
const previousEntity = usePreviousEntity();
const { checkUnsavedChanges } = useContext(UnsavedActions);
+ const entitiesList = useContext(EntitiesList);
+
+ const goToStringList = () => {
+ entitiesList.show(true);
+ };
const copyLinkToClipboard = useCallback(async () => {
const { locale, project, resource, entity } = location;
@@ -80,6 +86,19 @@ export function EntityNavigation(): React.ReactElement {
return (
+ }}
+ >
+
+