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 (
+ }} + > + +