partial: tailwind and first component migration

This commit is contained in:
Rafael Taranto 2025-04-24 11:14:01 +01:00
parent 866da2fc64
commit 6f10977fd0
12 changed files with 1269 additions and 389 deletions

File diff suppressed because it is too large Load diff

View file

@ -44,6 +44,7 @@
},
"devDependencies": {
"@eslint/js": "^9.16.0",
"@tailwindcss/vite": "^4.1.4",
"@vitejs/plugin-react-swc": "^3.7.2",
"esbuild-plugin-react-virtualized": "^1.0.4",
"eslint": "^9.16.0",
@ -53,6 +54,7 @@
"globals": "^15.13.0",
"lint-staged": "^15.2.10",
"prettier": "3.4.1",
"tailwindcss": "^4.1.4",
"vite": "^6.0.1",
"vite-plugin-svgr": "^4.3.0"
},

View file

@ -1,136 +1,22 @@
import { useQuery, gql } from '@apollo/client'
import CssBaseline from '@mui/material/CssBaseline'
import Grid from '@mui/material/Grid'
import Slide from '@mui/material/Slide'
import { StylesProvider, jssPreset, makeStyles } from '@mui/styles'
import { StylesProvider, jssPreset } from '@mui/styles'
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles'
import { create } from 'jss'
import extendJss from 'jss-plugin-extend'
import React, { useContext, useState } from 'react'
import {
useLocation,
useHistory,
BrowserRouter as Router
} from 'react-router-dom'
import Header from 'src/components/layout/Header'
import Sidebar from 'src/components/layout/Sidebar'
import TitleSection from 'src/components/layout/TitleSection'
import { tree, hasSidebar, Routes, getParent } from 'src/routing/routes'
import React, { useState } from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import ApolloProvider from 'src/utils/apollo'
import AppContext from 'src/AppContext'
import theme from 'src/styling/theme'
import { backgroundColor, mainWidth } from 'src/styling/variables'
import Main from './Main'
import './styling/global/global.css'
const jss = create({
plugins: [extendJss(), ...jssPreset().plugins]
})
const fill = '100%'
const flexDirection = 'column'
const useStyles = makeStyles({
root: {
backgroundColor,
width: fill,
minHeight: fill,
display: 'flex',
flexDirection
},
wrapper: {
width: mainWidth,
height: fill,
margin: '0 auto',
flex: 1,
display: 'flex',
flexDirection
},
grid: {
flex: 1,
height: '100%'
},
contentWithSidebar: {
flex: 1,
marginLeft: 48,
paddingTop: 15
},
contentWithoutSidebar: {
width: mainWidth
}
})
const GET_USER_DATA = gql`
query userData {
userData {
id
username
role
enabled
last_accessed
last_accessed_from
last_accessed_address
}
}
`
const Main = () => {
const classes = useStyles()
const location = useLocation()
const history = useHistory()
const { wizardTested, userData, setUserData } = useContext(AppContext)
const { loading } = useQuery(GET_USER_DATA, {
onCompleted: userResponse => {
if (!userData && userResponse?.userData)
setUserData(userResponse.userData)
}
})
const route = location.pathname
const sidebar = hasSidebar(route)
const parent = sidebar ? getParent(route) : {}
const is404 = location.pathname === '/404'
const isSelected = it => location.pathname === it.route
const onClick = it => history.push(it.route)
const contentClassName = sidebar
? classes.contentWithSidebar
: classes.contentWithoutSidebar
return (
<div className={classes.root}>
{!is404 && wizardTested && userData && (
<Header tree={tree} user={userData} />
)}
<main className={classes.wrapper}>
{sidebar && !is404 && wizardTested && (
<Slide direction="left" in={true} mountOnEnter unmountOnExit>
<div>
<TitleSection title={parent.title}></TitleSection>
</div>
</Slide>
)}
<Grid container className={classes.grid}>
{sidebar && !is404 && wizardTested && (
<Sidebar
data={parent.children}
isSelected={isSelected}
displayName={it => it.label}
onClick={onClick}
/>
)}
<div className={contentClassName}>{!loading && <Routes />}</div>
</Grid>
</main>
</div>
)
}
const App = () => {
const [wizardTested, setWizardTested] = useState(false)
const [userData, setUserData] = useState(null)

View file

@ -0,0 +1,83 @@
import { useHistory, useLocation } from 'react-router-dom'
import React, { useContext } from 'react'
import { gql, useQuery } from '@apollo/client'
import Slide from '@mui/material/Slide'
import Grid from '@mui/material/Grid'
import Header from './components/layout/Header.jsx'
import Sidebar from './components/layout/Sidebar.jsx'
import TitleSection from './components/layout/TitleSection.jsx'
import { getParent, hasSidebar, Routes, tree } from './routing/routes.jsx'
import AppContext from './AppContext.js'
const GET_USER_DATA = gql`
query userData {
userData {
id
username
role
enabled
last_accessed
last_accessed_from
last_accessed_address
}
}
`
const Main = () => {
const location = useLocation()
const history = useHistory()
const { wizardTested, userData, setUserData } = useContext(AppContext)
const { loading } = useQuery(GET_USER_DATA, {
onCompleted: userResponse => {
if (!userData && userResponse?.userData)
setUserData(userResponse.userData)
}
})
const route = location.pathname
const sidebar = hasSidebar(route)
const parent = sidebar ? getParent(route) : {}
const is404 = location.pathname === '/404'
const isSelected = it => location.pathname === it.route
const onClick = it => history.push(it.route)
const contentClassName = sidebar ? 'flex-1 ml-12 pt-4' : 'w-[1200px]'
return (
<div className="flex flex-col w-full min-h-full">
{!is404 && wizardTested && userData && (
<Header tree={tree} user={userData} />
)}
<main className="flex flex-1 flex-col my-0 mx-auto h-full w-[1200px]">
{sidebar && !is404 && wizardTested && (
<Slide direction="left" in={true} mountOnEnter unmountOnExit>
<div>
<TitleSection title={parent.title}></TitleSection>
</div>
</Slide>
)}
<Grid sx={{ flex: 1, height: 1 }} container>
{sidebar && !is404 && wizardTested && (
<Sidebar
data={parent.children}
isSelected={isSelected}
displayName={it => it.label}
onClick={onClick}
/>
)}
<div className={contentClassName}>{!loading && <Routes />}</div>
</Grid>
</main>
</div>
)
}
export default Main

View file

@ -1,5 +1,3 @@
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import { useFormikContext, Form, Formik, Field as FormikField } from 'formik'
import * as R from 'ramda'
import React, { useState, memo } from 'react'
@ -15,10 +13,6 @@ import { Link, IconButton } from 'src/components/buttons'
import { RadioGroup } from 'src/components/inputs/formik'
import { Table, TableBody, TableRow, TableCell } from 'src/components/table'
import { booleanPropertiesTableStyles } from './BooleanPropertiesTable.styles'
const useStyles = makeStyles(booleanPropertiesTableStyles)
const BooleanCell = ({ name }) => {
const { values } = useFormikContext()
return values[name] === 'true' ? <TrueIcon /> : <FalseIcon />
@ -26,6 +20,8 @@ const BooleanCell = ({ name }) => {
const BooleanPropertiesTable = memo(
({ title, disabled, data, elements, save, forcedEditing = false }) => {
const [editing, setEditing] = useState(forcedEditing)
const initialValues = R.fromPairs(
elements.map(it => [it.name, data[it.name]?.toString() ?? 'false'])
)
@ -39,10 +35,6 @@ const BooleanPropertiesTable = memo(
)
)
const [editing, setEditing] = useState(forcedEditing)
const classes = useStyles()
const innerSave = async values => {
const toBoolean = (num, _) => R.equals(num, 'true')
save(R.mapObjIndexed(toBoolean, R.filter(R.complement(R.isNil))(values)))
@ -54,7 +46,7 @@ const BooleanPropertiesTable = memo(
{ display: 'No', code: 'false' }
]
return (
<div className={classes.booleanPropertiesTableWrapper}>
<div className="flex w-sm flex-col ">
<Formik
validateOnBlur={false}
validateOnChange={false}
@ -65,16 +57,16 @@ const BooleanPropertiesTable = memo(
{({ resetForm }) => {
return (
<Form>
<div className={classes.rowWrapper}>
<div className="flex items-center">
<H4>{title}</H4>
{editing ? (
<div className={classes.rightAligned}>
<div className="ml-auto">
<Link type="submit" color="primary">
Save
</Link>
<Link
type="reset"
className={classes.rightLink}
className="ml-5"
onClick={() => {
resetForm()
setEditing(false)
@ -85,7 +77,7 @@ const BooleanPropertiesTable = memo(
</div>
) : (
<IconButton
className={classes.transparentButton}
className="my-auto mx-3"
onClick={() => setEditing(true)}
size="large">
{disabled ? <EditIconDisabled /> : <EditIcon />}
@ -93,26 +85,21 @@ const BooleanPropertiesTable = memo(
)}
</div>
<PromptWhenDirty />
<Table className={classes.fillColumn}>
<TableBody className={classes.fillColumn}>
<Table className="w-full">
<TableBody className="w-full">
{elements.map((it, idx) => (
<TableRow
key={idx}
size="sm"
className={classes.tableRow}>
<TableCell className={classes.leftTableCell}>
{it.display}
</TableCell>
<TableCell className={classes.rightTableCell}>
className="h-auto py-2 px-4 flex items-center justify-between min-h-8 even:bg-transparent odd:bg-zircon">
<TableCell className="p-0 w-50">{it.display}</TableCell>
<TableCell className="p-0 flex">
{editing && (
<FormikField
component={RadioGroup}
name={it.name}
options={radioButtonOptions}
className={classnames(
classes.radioButtons,
classes.rightTableCell
)}
className="flex flex-row m-[-15px] p-0"
/>
)}
{!editing && <BooleanCell name={it.name} />}
@ -122,11 +109,11 @@ const BooleanPropertiesTable = memo(
</TableBody>
</Table>
</Form>
);
)
}}
</Formik>
</div>
);
)
}
)

View file

@ -1,74 +0,0 @@
import baseStyles from 'src/pages/Logs.styles'
import { backgroundColor, zircon } from 'src/styling/variables'
const { fillColumn } = baseStyles
const booleanPropertiesTableStyles = {
booleanPropertiesTableWrapper: {
display: 'flex',
flexDirection: 'column',
width: 396
},
tableRow: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
'&:nth-child(even)': {
backgroundColor: backgroundColor
},
'&:nth-child(odd)': {
backgroundColor: zircon
},
minHeight: 32,
height: 'auto',
padding: [[8, 16, 8, 24]],
boxShadow: '0 0 0 0 rgba(0, 0, 0, 0)'
},
leftTableCell: {
display: 'flex',
alignItems: 'center',
justifyContent: 'left',
width: 200,
padding: [0]
},
rightTableCell: {
display: 'flex',
alignItems: 'center',
justifyContent: 'right',
padding: [0]
},
transparentButton: {
'& > *': {
margin: 'auto 12px'
},
'& button': {
border: 'none',
backgroundColor: 'transparent',
cursor: 'pointer'
}
},
rowWrapper: {
display: 'flex',
alignItems: 'center',
position: 'relative',
flex: 'wrap'
},
rightAligned: {
marginLeft: 'auto'
},
radioButtons: {
display: 'flex',
flexDirection: 'row',
margin: [-15]
},
rightLink: {
marginLeft: '20px'
},
fillColumn,
popoverContent: {
width: 272,
padding: [[10, 15]]
}
}
export { booleanPropertiesTableStyles }

View file

@ -0,0 +1,82 @@
/*!
* Web Fonts from Fontspring.com
*
* All OpenType features and all extended glyphs have been removed.
* Fully installable fonts can be purchased at http://www.fontspring.com
*
* The fonts included in this stylesheet are subject to the End User License you purchased
* from Fontspring. The fonts are protected under domestic and international trademark and
* copyright law. You are prohibited from modifying, reverse engineering, duplicating, or
* distributing this font software.
*
* (c) 2010-2018 Fontspring
*
*
*
*
* The fonts included are copyrighted by the vendor listed below.
*
* Vendor: Fontfabric
* License URL: https://www.fontspring.com/licenses/fontfabric/webfont
*
*
*/
@font-face {
font-family: 'Mont';
font-weight: 900;
font-style: normal;
src: url('/fonts/MontHeavy/mont-heavy-webfont.woff2') format('woff2'), url('/fonts/MontHeavy/mont-heavy-webfont.woff') format('woff');
}
@font-face {
font-family: 'Mont';
font-weight: 700;
font-style: normal;
src: url('/fonts/MontHeavy/mont-bold-webfont.woff2') format('woff2'), url('/fonts/MontHeavy/mont-bold-webfont.woff') format('woff');
}
/*!
* Web Fonts from Fontspring.com
*
* All OpenType features and all extended glyphs have been removed.
* Fully installable fonts can be purchased at http://www.fontspring.com
*
* The fonts included in this stylesheet are subject to the End User License you purchased
* from Fontspring. The fonts are protected under domestic and international trademark and
* copyright law. You are prohibited from modifying, reverse engineering, duplicating, or
* distributing this font software.
*
* (c) 2010-2018 Fontspring
*
*
*
*
* The fonts included are copyrighted by the vendor listed below.
*
* Vendor: exljbris Font Foundry
* License URL: https://www.fontspring.com/licenses/exljbris/webfont
*
*
*/
@font-face {
font-family: 'MuseoSans';
font-weight: 500;
font-style: normal;
src: url("/fonts/MuseoSans/MuseoSans_500-webfont.woff2") format("woff2"), url("/fonts/MuseoSans/MuseoSans_500-webfont.woff") format("woff");
}
@font-face {
font-family: 'MuseoSans';
font-weight: 700;
font-style: normal;
src: url("/fonts/MuseoSans/MuseoSans_700-webfont.woff2") format("woff2"), url("/fonts/MuseoSans/MuseoSans_700-webfont.woff") format("woff");
}
/* BP-mono Freely distributed at http://backpacker.gr/fonts/5 */
@font-face {
font-family: 'BPmono';
font-weight: 500;
font-style: normal;
src: url("/fonts/BPmono/BPmono.ttf") format("truetype"),
}

View file

@ -1,82 +0,0 @@
export default `
/*!
* Web Fonts from Fontspring.com
*
* All OpenType features and all extended glyphs have been removed.
* Fully installable fonts can be purchased at http://www.fontspring.com
*
* The fonts included in this stylesheet are subject to the End User License you purchased
* from Fontspring. The fonts are protected under domestic and international trademark and
* copyright law. You are prohibited from modifying, reverse engineering, duplicating, or
* distributing this font software.
*
* (c) 2010-2018 Fontspring
*
*
*
*
* The fonts included are copyrighted by the vendor listed below.
*
* Vendor: Fontfabric
* License URL: https://www.fontspring.com/licenses/fontfabric/webfont
*
*
*/
@font-face {
font-family: 'Mont';
font-weight: 900;
font-style: normal;
src: url('/fonts/MontHeavy/mont-heavy-webfont.woff2') format('woff2'), url('/fonts/MontHeavy/mont-heavy-webfont.woff') format('woff');
}
@font-face {
font-family: 'Mont';
font-weight: 700;
font-style: normal;
src: url('/fonts/MontHeavy/mont-bold-webfont.woff2') format('woff2'), url('/fonts/MontHeavy/mont-bold-webfont.woff') format('woff');
}
/*!
* Web Fonts from Fontspring.com
*
* All OpenType features and all extended glyphs have been removed.
* Fully installable fonts can be purchased at http://www.fontspring.com
*
* The fonts included in this stylesheet are subject to the End User License you purchased
* from Fontspring. The fonts are protected under domestic and international trademark and
* copyright law. You are prohibited from modifying, reverse engineering, duplicating, or
* distributing this font software.
*
* (c) 2010-2018 Fontspring
*
*
*
*
* The fonts included are copyrighted by the vendor listed below.
*
* Vendor: exljbris Font Foundry
* License URL: https://www.fontspring.com/licenses/exljbris/webfont
*
*
*/
@font-face {
font-family: 'MuseoSans';
font-weight: 500;
font-style: normal;
src: url("/fonts/MuseoSans/MuseoSans_500-webfont.woff2") format("woff2"), url("/fonts/MuseoSans/MuseoSans_500-webfont.woff") format("woff");
},
@font-face {
font-family: 'MuseoSans';
font-weight: 700;
font-style: normal;
src: url("/fonts/MuseoSans/MuseoSans_700-webfont.woff2") format("woff2"), url("/fonts/MuseoSans/MuseoSans_700-webfont.woff") format("woff");
},
/* BP-mono Freely distributed at http://backpacker.gr/fonts/5 */
@font-face {
font-family: 'BPmono';
font-weight: 500;
font-style: normal;
src: url("/fonts/BPmono/BPmono.ttf") format("truetype"),
}
`

View file

@ -0,0 +1,97 @@
@import "./fonts.css";
/*@tailwind setup with no preflighrt*/
/* TODO remove important after mui v6 migration*/
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities) important;
:root {
--zodiac: #1b2559;
--spring: #48f694;
--ghost: #fafbff;
--zircon: #ebefff;
}
@theme {
--color-zodiac: var(--zodiac);
--color-spring: var(--spring);
--color-ghost: var(--ghost);
--color-zircon: var(--zircon);
}
body {
font-size: 0.875rem;
width: 1200px;
color: var(--zodiac);
display: flex;
min-height: 100%;
@media screen and (min-width: 1200px) {
width: auto;
}
}
html {
height: 100%;
@media screen and (max-height: 900px) {
scrollbar-gutter: stable;
}
}
#root {
width: 100%;
min-height: 100%;
}
.root-notifcenter-open {
/*for when notification center is open*/
overflow-y: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
}
.body-notifcenter-open {
/*for when notification center is open*/
overflow: hidden;
}
.root-blur {
filter: blur(1px);
pointer-events: none;
}
a::-moz-focus-inner,
input[type="submit"]::-moz-focus-inner,
input[type="button"]::-moz-focus-inner {
border: 0;
}
a::-moz-focus-inner,
input[type="submit"]::-moz-focus-inner,
input[type="button"]::-moz-focus-inner {
border: 0;
}
a,
a:visited,
a:focus,
a:active,
a:hover {
outline: 0 none;
}
button::-moz-focus-inner {
border: 0;
}
/*forcing styling onto inner container*/
.ReactVirtualized__Grid__innerScrollContainer {
overflow: inherit !important;
}
.ReactVirtualized__Grid.ReactVirtualized__List {
overflow-y: auto !important;
}

View file

@ -1,80 +0,0 @@
import { mainWidth } from 'src/styling/variables'
import fonts from './fonts'
const fill = '100%'
export default `
${fonts}
body {
font-size: 0.875rem;
width: ${mainWidth}px;
display: flex;
min-height: ${fill};
@media screen and (min-width: 1200px) {
width: auto;
}
}
html {
height: ${fill};
@media screen and (max-height: 900px) {
scrollbar-gutter: stable;
}
}
#root {
width: ${fill};
minHeight: ${fill};
}
.root-notifcenter-open {
// for when notification center is open
overflow-y: 'auto';
position: 'absolute';
top: 0;
bottom: 0;
left: 0;
}
.body-notifcenter-open {
// for when notification center is open
overflow: hidden;
}
.root-blur {
filter: blur(1px);
pointer-events: none;
}
a::-moz-focus-inner,
input[type="submit"]::-moz-focus-inner,
input[type="button"]::-moz-focus-inner {
border: 0;
}
a::-moz-focus-inner,
input[type="submit"]::-moz-focus-inner,
input[type="button"]::-moz-focus-inner {
border: 0;
}
a,
a:visited,
a:focus,
a:active,
a:hover {
outline: 0 none;
}
button::-moz-focus-inner {
border: 0;
}
// forcing styling onto inner container
.ReactVirtualized__Grid__innerScrollContainer {
overflow: inherit !important;
},
.ReactVirtualized__Grid.ReactVirtualized__List {
overflow-y: overlay !important;
}
`

View file

@ -1,5 +1,4 @@
import { createTheme } from '@mui/material/styles'
import global from './global'
import typographyStyles from 'src/components/typography/styles'
@ -45,9 +44,6 @@ let theme = createTheme({
theme = createTheme(theme, {
components: {
MuiCssBaseline: {
styleOverrides: global
},
MuiTypography: {
styleOverrides: {
root: { ...p },

View file

@ -4,6 +4,7 @@ import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import svgr from 'vite-plugin-svgr'
import fixReactVirtualized from 'esbuild-plugin-react-virtualized'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
base: '/',
@ -25,7 +26,7 @@ export default defineConfig({
plugins: [fixReactVirtualized]
}
},
plugins: [react(), svgr()],
plugins: [react(), svgr(), tailwindcss()],
resolve: {
alias: {
src: fileURLToPath(new URL('./src', import.meta.url))