Web app: add RTL support
Ref: https://mui.com/material-ui/guides/right-to-left https://m2.material.io/design/usability/bidirectionality.html
This commit is contained in:
		
							parent
							
								
									4267c0d9b6
								
							
						
					
					
						commit
						7a1488fcd3
					
				
					 4 changed files with 96 additions and 27 deletions
				
			
		
							
								
								
									
										40
									
								
								web/package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										40
									
								
								web/package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -8,6 +8,7 @@ | |||
|       "name": "ntfy", | ||||
|       "version": "1.0.0", | ||||
|       "dependencies": { | ||||
|         "@emotion/cache": "^11.11.0", | ||||
|         "@emotion/react": "^11.11.0", | ||||
|         "@emotion/styled": "^11.11.0", | ||||
|         "@mui/icons-material": "^5.4.2", | ||||
|  | @ -25,7 +26,9 @@ | |||
|         "react-infinite-scroll-component": "^6.1.0", | ||||
|         "react-router-dom": "^6.2.2", | ||||
|         "stacktrace-gps": "^3.0.4", | ||||
|         "stacktrace-js": "^2.0.2" | ||||
|         "stacktrace-js": "^2.0.2", | ||||
|         "stylis": "^4.3.0", | ||||
|         "stylis-plugin-rtl": "^2.1.1" | ||||
|       }, | ||||
|       "devDependencies": { | ||||
|         "@vitejs/plugin-react": "^4.0.0", | ||||
|  | @ -1765,6 +1768,11 @@ | |||
|         "stylis": "4.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/babel-plugin/node_modules/stylis": { | ||||
|       "version": "4.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", | ||||
|       "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" | ||||
|     }, | ||||
|     "node_modules/@emotion/cache": { | ||||
|       "version": "11.11.0", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", | ||||
|  | @ -1777,6 +1785,11 @@ | |||
|         "stylis": "4.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@emotion/cache/node_modules/stylis": { | ||||
|       "version": "4.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", | ||||
|       "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" | ||||
|     }, | ||||
|     "node_modules/@emotion/hash": { | ||||
|       "version": "0.9.1", | ||||
|       "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", | ||||
|  | @ -3314,6 +3327,14 @@ | |||
|         "node": ">=8" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/cssjanus": { | ||||
|       "version": "2.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/cssjanus/-/cssjanus-2.1.0.tgz", | ||||
|       "integrity": "sha512-kAijbny3GmdOi9k+QT6DGIXqFvL96aksNlGr4Rhk9qXDZYWUojU4bRc3IHWxdaLNOqgEZHuXoe5Wl2l7dxLW5g==", | ||||
|       "engines": { | ||||
|         "node": ">=10.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/csstype": { | ||||
|       "version": "3.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", | ||||
|  | @ -6351,9 +6372,20 @@ | |||
|       } | ||||
|     }, | ||||
|     "node_modules/stylis": { | ||||
|       "version": "4.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", | ||||
|       "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" | ||||
|       "version": "4.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", | ||||
|       "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" | ||||
|     }, | ||||
|     "node_modules/stylis-plugin-rtl": { | ||||
|       "version": "2.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/stylis-plugin-rtl/-/stylis-plugin-rtl-2.1.1.tgz", | ||||
|       "integrity": "sha512-q6xIkri6fBufIO/sV55md2CbgS5c6gg9EhSVATtHHCdOnbN/jcI0u3lYhNVeuI65c4lQPo67g8xmq5jrREvzlg==", | ||||
|       "dependencies": { | ||||
|         "cssjanus": "^2.0.1" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "stylis": "4.x" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/supports-color": { | ||||
|       "version": "5.5.0", | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ | |||
|     "lint": "eslint --report-unused-disable-directives --ext .js,.jsx ./src/" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@emotion/cache": "^11.11.0", | ||||
|     "@emotion/react": "^11.11.0", | ||||
|     "@emotion/styled": "^11.11.0", | ||||
|     "@mui/icons-material": "^5.4.2", | ||||
|  | @ -28,7 +29,9 @@ | |||
|     "react-infinite-scroll-component": "^6.1.0", | ||||
|     "react-router-dom": "^6.2.2", | ||||
|     "stacktrace-gps": "^3.0.4", | ||||
|     "stacktrace-js": "^2.0.2" | ||||
|     "stacktrace-js": "^2.0.2", | ||||
|     "stylis": "^4.3.0", | ||||
|     "stylis-plugin-rtl": "^2.1.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@vitejs/plugin-react": "^4.0.0", | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import { createContext, Suspense, useContext, useEffect, useState, useMemo } fro | |||
| import { Box, Toolbar, CssBaseline, Backdrop, CircularProgress, useMediaQuery, ThemeProvider, createTheme } from "@mui/material"; | ||||
| import { useLiveQuery } from "dexie-react-hooks"; | ||||
| import { BrowserRouter, Outlet, Route, Routes, useParams } from "react-router-dom"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import { AllSubscriptions, SingleSubscription } from "./Notifications"; | ||||
| import { darkTheme, lightTheme } from "./theme"; | ||||
| import Navigation from "./Navigation"; | ||||
|  | @ -21,6 +22,7 @@ import Signup from "./Signup"; | |||
| import Account from "./Account"; | ||||
| import "../app/i18n"; // Translations! | ||||
| import prefs, { THEME } from "../app/Prefs"; | ||||
| import RTLCacheProvider from "./RTLCacheProvider"; | ||||
| 
 | ||||
| export const AccountContext = createContext(null); | ||||
| 
 | ||||
|  | @ -39,17 +41,26 @@ const darkModeEnabled = (prefersDarkMode, themePreference) => { | |||
| }; | ||||
| 
 | ||||
| const App = () => { | ||||
|   const { i18n } = useTranslation(); | ||||
|   const languageDir = i18n.dir(); | ||||
| 
 | ||||
|   const [account, setAccount] = useState(null); | ||||
|   const accountMemo = useMemo(() => ({ account, setAccount }), [account, setAccount]); | ||||
|   const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); | ||||
|   const themePreference = useLiveQuery(() => prefs.theme()); | ||||
|   const theme = React.useMemo( | ||||
|     () => createTheme(darkModeEnabled(prefersDarkMode, themePreference) ? darkTheme : lightTheme), | ||||
|     [prefersDarkMode, themePreference] | ||||
|     () => createTheme({ ...(darkModeEnabled(prefersDarkMode, themePreference) ? darkTheme : lightTheme), direction: languageDir }), | ||||
|     [prefersDarkMode, themePreference, languageDir] | ||||
|   ); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     document.documentElement.setAttribute("lang", i18n.language); | ||||
|     document.dir = languageDir; | ||||
|   }, [i18n.language, languageDir]); | ||||
| 
 | ||||
|   return ( | ||||
|     <Suspense fallback={<Loader />}> | ||||
|       <RTLCacheProvider> | ||||
|         <BrowserRouter> | ||||
|           <ThemeProvider theme={theme}> | ||||
|             <AccountContext.Provider value={accountMemo}> | ||||
|  | @ -70,6 +81,7 @@ const App = () => { | |||
|             </AccountContext.Provider> | ||||
|           </ThemeProvider> | ||||
|         </BrowserRouter> | ||||
|       </RTLCacheProvider> | ||||
|     </Suspense> | ||||
|   ); | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										22
									
								
								web/src/components/RTLCacheProvider.jsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								web/src/components/RTLCacheProvider.jsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| import React from "react"; | ||||
| 
 | ||||
| import rtlPlugin from "stylis-plugin-rtl"; | ||||
| import { CacheProvider } from "@emotion/react"; | ||||
| import createCache from "@emotion/cache"; | ||||
| import { prefixer } from "stylis"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| 
 | ||||
| // https://mui.com/material-ui/guides/right-to-left | ||||
| 
 | ||||
| const cacheRtl = createCache({ | ||||
|   key: "muirtl", | ||||
|   stylisPlugins: [prefixer, rtlPlugin], | ||||
| }); | ||||
| 
 | ||||
| const RTLCacheProvider = ({ children }) => { | ||||
|   const { i18n } = useTranslation(); | ||||
| 
 | ||||
|   return i18n.dir() === "rtl" ? <CacheProvider value={cacheRtl}>{children}</CacheProvider> : children; | ||||
| }; | ||||
| 
 | ||||
| export default RTLCacheProvider; | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue