refactor(styling): Remove Tailwind (#253)

* refactor(css): replace Tailwind classes with MUI sx
* chore(deps): remove classnames
* refactor(css): inline link baseline style
* feat(css): use modern-normalize
* chore(deps): remove tailwind
* refactor(css): inline all Sass files
* chore(deps): remove sass
This commit is contained in:
Jeremy Kahn 2024-04-09 20:54:41 -05:00 committed by GitHub
parent 72526ebbbb
commit 6c434f84ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 179 additions and 1098 deletions

1044
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,13 +19,13 @@
"@types/react": "^18.2.66", "@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^18.2.22",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"classnames": "^2.3.1",
"detectincognitojs": "^1.1.2", "detectincognitojs": "^1.1.2",
"fast-memoize": "^2.5.2", "fast-memoize": "^2.5.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"fun-animal-names": "^0.1.1", "fun-animal-names": "^0.1.1",
"idb-chunk-store": "^1.0.1", "idb-chunk-store": "^1.0.1",
"localforage": "^1.10.0", "localforage": "^1.10.0",
"modern-normalize": "^2.0.0",
"mui-markdown": "^0.5.5", "mui-markdown": "^0.5.5",
"querystring": "^0.2.1", "querystring": "^0.2.1",
"react": "^18.2.0", "react": "^18.2.0",
@ -113,13 +113,10 @@
"mprocs": "^0.6.4", "mprocs": "^0.6.4",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"parcel": "^2.10.0", "parcel": "^2.10.0",
"postcss": "^8.4.31",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"pretty-quick": "^4.0.0", "pretty-quick": "^4.0.0",
"process": "^0.11.10", "process": "^0.11.10",
"sass": "^1.69.5",
"serve": "^14.1.2", "serve": "^14.1.2",
"tailwindcss": "^3.1.8",
"url": "^0.11.0", "url": "^0.11.0",
"util": "^0.12.5", "util": "^0.12.5",
"vite": "^5.0.13", "vite": "^5.0.13",

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,5 +1,4 @@
import { HTMLAttributes, useRef, useEffect, useState, useContext } from 'react' import { HTMLAttributes, useRef, useEffect, useState, useContext } from 'react'
import cx from 'classnames'
import Box from '@mui/material/Box' import Box from '@mui/material/Box'
import useTheme from '@mui/material/styles/useTheme' import useTheme from '@mui/material/styles/useTheme'
@ -12,11 +11,7 @@ export interface ChatTranscriptProps extends HTMLAttributes<HTMLDivElement> {
userId: string userId: string
} }
export const ChatTranscript = ({ export const ChatTranscript = ({ messageLog, userId }: ChatTranscriptProps) => {
className,
messageLog,
userId,
}: ChatTranscriptProps) => {
const { showRoomControls } = useContext(ShellContext) const { showRoomControls } = useContext(ShellContext)
const theme = useTheme() const theme = useTheme()
const boxRef = useRef<HTMLDivElement>(null) const boxRef = useRef<HTMLDivElement>(null)
@ -66,11 +61,13 @@ export const ChatTranscript = ({
return ( return (
<Box <Box
ref={boxRef} ref={boxRef}
className={cx('ChatTranscript', className)} className="ChatTranscript"
sx={{ sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
py: transcriptMinPadding, flexGrow: 1,
overflow: 'auto',
pb: transcriptMinPadding,
pt: showRoomControls ? theme.spacing(10) : theme.spacing(2), pt: showRoomControls ? theme.spacing(10) : theme.spacing(2),
px: `max(${transcriptPaddingX}, ${transcriptMinPadding})`, px: `max(${transcriptPaddingX}, ${transcriptMinPadding})`,
transition: `padding-top ${theme.transitions.duration.short}ms ${theme.transitions.easing.easeInOut}`, transition: `padding-top ${theme.transitions.duration.short}ms ${theme.transitions.easing.easeInOut}`,

View File

@ -0,0 +1,9 @@
import styled from '@mui/material/styles/styled'
// NOTE: These components are defined to enable raw DOM elements to be styled
// with MUI's sx prop.
// @see https://mui.com/system/styled/
// @see https://mui.com/system/getting-started/the-sx-prop/
export const Form = styled('form')({})
export const Input = styled('input')({})
export const Main = styled('main')({})

View File

@ -1,3 +0,0 @@
.Message
pre
overflow: auto

View File

@ -5,20 +5,10 @@ import Box from '@mui/material/Box'
import Tooltip from '@mui/material/Tooltip' import Tooltip from '@mui/material/Tooltip'
import Typography, { TypographyProps } from '@mui/material/Typography' import Typography, { TypographyProps } from '@mui/material/Typography'
import Link, { LinkProps } from '@mui/material/Link' import Link, { LinkProps } from '@mui/material/Link'
import styled from '@mui/material/styles/styled'
import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism' import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
// These imports need to be ts-ignored to prevent spurious errors that look
// like this:
//
// Module 'react-markdown' cannot be imported using this construct. The
// specifier only resolves to an ES module, which cannot be imported
// synchronously. Use dynamic import instead. (tsserver 1471)
//
// @ts-ignore
import Markdown from 'react-markdown' import Markdown from 'react-markdown'
// @ts-ignore
import { CodeProps } from 'react-markdown/lib/ast-to-react' import { CodeProps } from 'react-markdown/lib/ast-to-react'
// @ts-ignore
import remarkGfm from 'remark-gfm' import remarkGfm from 'remark-gfm'
import { import {
@ -32,7 +22,7 @@ import { CopyableBlock } from 'components/CopyableBlock/CopyableBlock'
import { InlineMedia } from './InlineMedia' import { InlineMedia } from './InlineMedia'
import './Message.sass' const StyledMarkdown = styled(Markdown)({})
export interface MessageProps { export interface MessageProps {
message: IMessage | I_InlineMedia message: IMessage | I_InlineMedia
@ -154,13 +144,26 @@ export const Message = ({ message, showAuthor, userId }: MessageProps) => {
) : isYouTubeLink(message) ? ( ) : isYouTubeLink(message) ? (
<YouTube videoId={getYouTubeVideoId(message.text)} /> <YouTube videoId={getYouTubeVideoId(message.text)} />
) : ( ) : (
<Markdown <StyledMarkdown
components={componentMap} components={componentMap}
remarkPlugins={[remarkGfm]} remarkPlugins={[remarkGfm]}
linkTarget="_blank" linkTarget="_blank"
sx={{
'& pre': {
overflow: 'auto',
},
'& ol': {
pl: 2,
listStyleType: 'decimal',
},
'& ul': {
pl: 2,
listStyleType: 'disc',
},
}}
> >
{message.text} {message.text}
</Markdown> </StyledMarkdown>
)} )}
</Box> </Box>
</Tooltip> </Tooltip>

View File

@ -14,7 +14,7 @@ import ArrowUpward from '@mui/icons-material/ArrowUpward'
import { messageCharacterSizeLimit } from 'config/messaging' import { messageCharacterSizeLimit } from 'config/messaging'
import { SettingsContext } from 'contexts/SettingsContext' import { SettingsContext } from 'contexts/SettingsContext'
import classNames from 'classnames' import { Form } from 'components/Elements'
interface MessageFormProps { interface MessageFormProps {
onMessageSubmit: (message: string) => void onMessageSubmit: (message: string) => void
@ -76,12 +76,17 @@ export const MessageForm = ({
} }
return ( return (
<form <Form
onSubmit={handleMessageSubmit} onSubmit={handleMessageSubmit}
className={classNames({ sx={{
'pt-4 px-4': showActiveTypingStatus, ...(showActiveTypingStatus && {
'p-4': !showActiveTypingStatus, pt: 2,
})} px: 2,
}),
...(!showActiveTypingStatus && {
p: 2,
}),
}}
> >
<Stack direction="row" spacing={2}> <Stack direction="row" spacing={2}>
<FormControl fullWidth> <FormControl fullWidth>
@ -110,6 +115,6 @@ export const MessageForm = ({
<ArrowUpward /> <ArrowUpward />
</Fab> </Fab>
</Stack> </Stack>
</form> </Form>
) )
} }

View File

@ -157,11 +157,7 @@ export function Room({
height: landscape ? '100%' : '40%', height: landscape ? '100%' : '40%',
}} }}
> >
<ChatTranscript <ChatTranscript messageLog={messageLog} userId={userId} />
messageLog={messageLog}
userId={userId}
className="grow overflow-auto"
/>
<Divider /> <Divider />
<Box> <Box>
<MessageForm <MessageForm

View File

@ -8,6 +8,8 @@ import CircularProgress from '@mui/material/CircularProgress'
import { RoomContext } from 'contexts/RoomContext' import { RoomContext } from 'contexts/RoomContext'
import { PeerRoom } from 'lib/PeerRoom' import { PeerRoom } from 'lib/PeerRoom'
import { Input } from 'components/Elements'
import { useRoomFileShare } from './useRoomFileShare' import { useRoomFileShare } from './useRoomFileShare'
import { MediaButton } from './MediaButton' import { MediaButton } from './MediaButton'
@ -73,12 +75,12 @@ export function RoomFileUploadControls({
px: 1, px: 1,
}} }}
> >
<input <Input
multiple multiple
ref={fileInputRef} ref={fileInputRef}
type="file" type="file"
id="file-upload" id="file-upload"
className="hidden" sx={{ display: 'none' }}
onChange={handleFileSelect} onChange={handleFileSelect}
/> />
<Tooltip <Tooltip

View File

@ -1,3 +0,0 @@
.PeerDownloadFileButton
.MuiCircularProgress-circle
transition: none !important

View File

@ -10,7 +10,6 @@ import { fileTransfer } from 'lib/FileTransfer'
import { Peer } from 'models/chat' import { Peer } from 'models/chat'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import './PeerDownloadFileButton.sass'
import { usePeerNameDisplay } from 'components/PeerNameDisplay/usePeerNameDisplay' import { usePeerNameDisplay } from 'components/PeerNameDisplay/usePeerNameDisplay'
interface PeerDownloadFileButtonProps { interface PeerDownloadFileButtonProps {
@ -65,6 +64,9 @@ export const PeerDownloadFileButton = ({
<CircularProgress <CircularProgress
variant={downloadProgress === null ? 'indeterminate' : 'determinate'} variant={downloadProgress === null ? 'indeterminate' : 'determinate'}
value={downloadProgress === null ? undefined : downloadProgress} value={downloadProgress === null ? undefined : downloadProgress}
sx={{
transition: 'none',
}}
/> />
) : ( ) : (
<Tooltip <Tooltip

20
src/index.css Normal file
View File

@ -0,0 +1,20 @@
a {
color: inherit;
text-decoration: inherit;
}
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}

View File

@ -1,9 +0,0 @@
@tailwind base
@tailwind components
@tailwind utilities
ol
@apply pl-4 list-decimal
ul
@apply pl-4 list-disc

View File

@ -2,7 +2,8 @@ import './polyfills'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import 'typeface-roboto' import 'typeface-roboto'
import './index.sass' import 'modern-normalize/modern-normalize.css'
import './index.css'
import { ThemeProvider, createTheme } from '@mui/material/styles' import { ThemeProvider, createTheme } from '@mui/material/styles'
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter' import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'

View File

@ -1,6 +1,7 @@
import { useContext, useEffect } from 'react' import { useContext, useEffect } from 'react'
import MuiMarkdown from 'mui-markdown' import MuiMarkdown from 'mui-markdown'
import Box from '@mui/material/Box' import Box from '@mui/material/Box'
import useTheme from '@mui/material/styles/useTheme'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { import {
@ -8,8 +9,6 @@ import {
messageCharacterSizeLimit, messageCharacterSizeLimit,
} from 'config/messaging' } from 'config/messaging'
import './index.sass'
const messageTranscriptSizeLimitFormatted = Intl.NumberFormat().format( const messageTranscriptSizeLimitFormatted = Intl.NumberFormat().format(
messageTranscriptSizeLimit messageTranscriptSizeLimit
) )
@ -20,13 +19,24 @@ const messageCharacterSizeLimitFormatted = Intl.NumberFormat().format(
export const About = () => { export const About = () => {
const { setTitle } = useContext(ShellContext) const { setTitle } = useContext(ShellContext)
const theme = useTheme()
useEffect(() => { useEffect(() => {
setTitle('About') setTitle('About')
}, [setTitle]) }, [setTitle])
return ( return (
<Box className="About max-w-3xl mx-auto p-4"> <Box
className="About"
sx={{
p: 2,
mx: 'auto',
maxWidth: theme.breakpoints.values.md,
'& p': {
mb: 2,
},
}}
>
<MuiMarkdown> <MuiMarkdown>
{` {`
### User Guide ### User Guide

View File

@ -1,3 +0,0 @@
.About
p
@apply mb-4

View File

@ -1,20 +1,30 @@
import { useContext, useEffect } from 'react' import { useContext, useEffect } from 'react'
import Box from '@mui/material/Box' import Box from '@mui/material/Box'
import MuiMarkdown from 'mui-markdown' import MuiMarkdown from 'mui-markdown'
import useTheme from '@mui/material/styles/useTheme'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import './index.sass'
export const Disclaimer = () => { export const Disclaimer = () => {
const { setTitle } = useContext(ShellContext) const { setTitle } = useContext(ShellContext)
const theme = useTheme()
useEffect(() => { useEffect(() => {
setTitle('Disclaimer') setTitle('Disclaimer')
}, [setTitle]) }, [setTitle])
return ( return (
<Box className="Disclaimer max-w-3xl mx-auto p-4"> <Box
className="Disclaimer"
sx={{
p: 2,
mx: 'auto',
maxWidth: theme.breakpoints.values.md,
'& p': {
mb: 2,
},
}}
>
<MuiMarkdown> <MuiMarkdown>
{` {`
### Interpretation and Definitions ### Interpretation and Definitions

View File

@ -1,6 +0,0 @@
.Disclaimer
ul
@apply my-4
p
@apply mb-4

View File

@ -10,22 +10,28 @@ import IconButton from '@mui/material/IconButton'
import MuiLink from '@mui/material/Link' import MuiLink from '@mui/material/Link'
import GitHubIcon from '@mui/icons-material/GitHub' import GitHubIcon from '@mui/icons-material/GitHub'
import Cached from '@mui/icons-material/Cached' import Cached from '@mui/icons-material/Cached'
import useTheme from '@mui/material/styles/useTheme'
import styled from '@mui/material/styles/styled'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { routes } from 'config/routes' import { routes } from 'config/routes'
import { ShellContext } from 'contexts/ShellContext' import { ShellContext } from 'contexts/ShellContext'
import { PeerNameDisplay } from 'components/PeerNameDisplay' import { PeerNameDisplay } from 'components/PeerNameDisplay'
import { Form, Main } from 'components/Elements'
import Logo from 'img/logo.svg?react' import Logo from 'img/logo.svg?react'
import { EmbedCodeDialog } from './EmbedCodeDialog' import { EmbedCodeDialog } from './EmbedCodeDialog'
const StyledLogo = styled(Logo)({})
interface HomeProps { interface HomeProps {
userId: string userId: string
} }
export function Home({ userId }: HomeProps) { export function Home({ userId }: HomeProps) {
const { setTitle } = useContext(ShellContext) const { setTitle } = useContext(ShellContext)
const theme = useTheme()
const [roomName, setRoomName] = useState(uuid()) const [roomName, setRoomName] = useState(uuid())
const [showEmbedCode, setShowEmbedCode] = useState(false) const [showEmbedCode, setShowEmbedCode] = useState(false)
const navigate = useNavigate() const navigate = useNavigate()
@ -63,11 +69,29 @@ export function Home({ userId }: HomeProps) {
return ( return (
<Box className="Home"> <Box className="Home">
<main className="mt-6 px-4 max-w-3xl text-center mx-auto"> <Main
sx={{
maxWidth: theme.breakpoints.values.md,
mt: 3,
mx: 'auto',
px: 2,
textAlign: 'center',
}}
>
<Link to={routes.ABOUT}> <Link to={routes.ABOUT}>
<Logo className="px-1 pb-4 mx-auto max-w-md" /> <StyledLogo
sx={{
px: 0.5,
pb: 2,
mx: 'auto',
maxWidth: theme.breakpoints.values.sm,
}}
/>
</Link> </Link>
<form onSubmit={handleFormSubmit} className="max-w-xl mx-auto"> <Form
onSubmit={handleFormSubmit}
sx={{ maxWidth: theme.breakpoints.values.sm, mx: 'auto' }}
>
<Typography sx={{ mb: 2 }}> <Typography sx={{ mb: 2 }}>
Your username:{' '} Your username:{' '}
<PeerNameDisplay paragraph={false} sx={{ fontWeight: 'bold' }}> <PeerNameDisplay paragraph={false} sx={{ fontWeight: 'bold' }}>
@ -135,10 +159,17 @@ export function Home({ userId }: HomeProps) {
Get embed code Get embed code
</Button> </Button>
</Box> </Box>
</form> </Form>
</main> </Main>
<Divider sx={{ my: 2 }} /> <Divider sx={{ my: 2 }} />
<Box className="max-w-3xl text-center mx-auto px-4"> <Box
sx={{
maxWidth: theme.breakpoints.values.sm,
mx: 'auto',
textAlign: 'center',
px: 2,
}}
>
<Typography variant="body1"> <Typography variant="body1">
This is a free communication tool that is designed for simplicity, This is a free communication tool that is designed for simplicity,
privacy, and security. All interaction between you and your online privacy, and security. All interaction between you and your online
@ -171,7 +202,7 @@ export function Home({ userId }: HomeProps) {
</IconButton> </IconButton>
</MuiLink> </MuiLink>
</Box> </Box>
<Typography variant="body1" sx={{ textAlign: 'center' }}> <Typography variant="body1" sx={{ textAlign: 'center', mb: 1 }}>
Licensed under{' '} Licensed under{' '}
<MuiLink <MuiLink
href="https://github.com/jeremyckahn/chitchatter/blob/develop/LICENSE" href="https://github.com/jeremyckahn/chitchatter/blob/develop/LICENSE"

View File

@ -118,7 +118,7 @@ export const Settings = ({ userId }: SettingsProps) => {
const areNotificationsAvailable = notification.permission === 'granted' const areNotificationsAvailable = notification.permission === 'granted'
return ( return (
<Box className="max-w-3xl mx-auto p-4"> <Box sx={{ p: 2, mx: 'auto', maxWidth: theme.breakpoints.values.md }}>
<Typography <Typography
variant="h2" variant="h2"
sx={{ sx={{

View File

@ -1,8 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {},
},
plugins: [],
}