TSS (tss-react)試用紀錄
React, CSS in JS, TypeScript, JSS, TSS
引言
簡單來說TSS就是JSS的TypeScript版本。JSS 主要有三個基本能力:
取得Theme
<ThemeProvider /> → useTheme()
自訂可共用性通用性的 CSS modules 與使用
makeStyles() → useStyles()
與自訂簡單 CSS 分隔特性的元件
withStyles() 或 styled()
簡單結論
styled()
函式單一元件 CSS in JS 。適用單 Element 客製 CSS 分隔元件,如:h1 ~ h6, p, button等。
自訂基礎元件開發階段。
withStyles()
函式複合元件 CSS in JS。適用複合 Element 客製 CSS 分隔元件,如:InputField, TextField等。
自訂基礎元件開發階段。
makeStyles() → useStyles()
函式有共用性,然實際上CSS分隔的應用沒見過過有人共用。
建立CSS modules再使用它(們)。
應用開發階段。
原碼紀錄
styled() 函式範例。
適用單 Element 客製 CSS 分隔元件,如:h1 ~ h6, p, button等。 對高等元件也有效但這實用意義不大。
import React from 'react'
import { styled } from '@mui/material'
// styled with theme
export const H1 = styled('h1')(({ theme }) => ({
color: theme.palette.primary.dark,
border: 'solid 2px pink',
borderRadius: '2px'
}));
// styled with theme & props
export const P1 = styled('p')<{
bold?: string // 自訂 props
}>(props => {
const { theme: { palette } } = props
console.log('P1', { palette, props })
return {
borderStyle: 'solid',
borderRadius: 3,
padding: 3,
color: palette.info.main,
borderColor: palette.warning.light,
borderWidth: props.bold === "true" ? 3 : 1,
fontWeight: props.bold === "true" ? 900 : 300
}
});
withStyles()
函式範例
withStyles()
函式範例適用複合 Element 客製 CSS 分隔元件,如:InputField, TextField等。
import type { FC } from 'react'
import React from 'react'
import { withStyles } from 'tss-react/mui'
import clsx from 'clsx'
const MyNote0: FC<{
className?: string,
children: React.ReactNode,
primary: boolean
}> = (props) => (
{/* ※ 注:外殼必需有 className={props.className} 屬性
來銜接 withStyle 產生的 root css-class。 */}
<div className={props.className}>
<p className={clsx(props.primary && 'primary')}>
{props.children}
</p>
</div>
)
export const MyNote = withStyles(ANote0, (theme: Theme, props) => {
const { palette, spacing } = theme
return {
root: {
background: palette.info.light,
color: palette.primary.dark,
'& .primary': {
padding: spacing(2),
background: palette.warning.light
}
}
}
})
withStyles
函式對高階元件也有效。
withStyles
函式對高階元件也有效。import type { FC } from 'react'
import React from 'react';
import { Switch, FormControlLabel, Theme } from '@mui/material'
import { withStyles } from 'tss-react/mui';
import clsx from 'clsx';
export const MySwitch: FC<{
label: string,
checked?: boolean,
onChange: (checked: boolean) => void,
className?: string
}> = (props) => (
<FormControlLabel className={props.className} label={props.label}
control={<Switch checked={props.checked} onChange={(event, checked) => props.onChange(checked)} />}
/>
)
export const StyledMySwitch = withStyles(MySwitch, (theme: Theme, props) => {
const { palette } = theme
return {
root: {
background: props.checked ? palette.secondary.main : palette.error.light,
color: palette.primary.dark,
}
}
}
應用
import { useReducer } from 'react'
import { Container } from '@mui/material'
import { H1, P1, MySwitch, StyledMySwitch as MySwitch2, ANote } from './widgets/StyledWidgets'
export default (props) => {
const [bold, toggleBold] = useReducer((f) => !f, false)
return (
<Container>
<H1>I am AppForm 0011</H1>
<MySwitch label="blob" checked={bold} onChange={toggleBold} />
<MySwitch2 label="blob2" checked={bold} onChange={toggleBold} />
<ANote primary={bold}>
this is foo.<br/>
that is bar.
</ANote>
<P1 bold={bold.toString()}>Show me the moeny. 111</P1>
</Container>
)
}
makeStyles → useStyels
用 makeStyels
建立CSS modules。
import type { Theme } from "@mui/material";
import { makeStyles } from 'tss-react/mui';
export const useStyles = makeStyles<{ param0: boolean }>()(
(theme: Theme, params) => {
const { palette } = theme
return {
root: {
},
todoItem: {
'&:nth-of-type(odd)': {
backgroundColor: palette.grey[100], // stripe, 產生條紋// stripe, 產生條紋
},
'&:hover': {
backgroundColor: palette.action.focus
},
'& button': {
color: 'transparent'
},
'&:hover button': {
color: palette.error.main
},
'&.completed span': {
color: palette.grey[500],
textDecoration: 'line-through'
},
},
}
}
);
再透過 useStyles
使用它(們)。
// type
import type { TodoItem } from 'views/demo2/dm2030/todoListSlice'
//
import { Container, Stack, Paper, IconButton, Button, InputAdornment } from '@mui/material'
import { List, ListItem, ListItemText, ListItemIcon } from '@mui/material'
import { OutlinedInput } from '@mui/material'
import { H3, P1 } from 'components/highorder'
import RadioField from './RadioFiled'
// hooks
import { useState } from 'react'
import { useAppSelector, useAppDispatch } from 'hooks/hooks'
import * as act from 'views/demo2/dm2030/todoListSlice'
// icons
import DoneIcon from '@mui/icons-material/CheckCircle';
import UndoIcon from '@mui/icons-material/RadioButtonUnchecked';
import ClearIcon from '@mui/icons-material/Clear'
import CheckIcon from '@mui/icons-material/Check'
// CSS
import clsx from 'clsx'
import { TransitionGroup, CSSTransition } from 'react-transition-group'
import { useStyles } from './AppForm.styles'
//import styels from './AppForm.module.css'
export default (props) => {
const todoList = useAppSelector(store => store.todoList)
const todoActiveCount = useAppSelector(act.activeCount)
const dispatch = useAppDispatch()
const [newText, setNewText] = useState('')
const [filterCond, setFilterCond] = useState('all')
const { classes: ss } = useStyles({ param0: false })
const filterHandler = (todo: TodoItem) => (
filterCond === 'all' ||
filterCond === 'active' && !todo.completed ||
filterCond === 'completed' && todo.completed
);
return (
<Container className={clsx(ss.root, props.className)}>
<H3>Todos</H3>
<P1>新增(mount)與移除(unmount)加入過場動畫。<br />
並條紋化與hover。<br />
參考:<a href="http://chenglou.github.io/react-motion/demos/demo3-todomvc-list-transition/" target="_blank">RedoMVC</a>
</P1>
<Paper sx={{ p: 2 }}>
<OutlinedInput placeholder="What needs to bo done?"
fullWidth
value={newText}
onChange={e => setNewText(e.target.value)}
onKeyUp={e => {
if (e.key === 'Enter') {
dispatch(act.addItem(newText))
setNewText('')
}
}}
startAdornment={
<InputAdornment position="start">
<CheckIcon sx={{ cursor: 'pointer' }} onClick={() => dispatch(act.checkAllItem())} />
</InputAdornment>
}
/>
<List>
<TransitionGroup>
{todoList.filter(filterHandler)
.filter(c => c.text && c.text.includes(newText))
.map((todo) => (
<CSSTransition
key={todo.id}
timeout={400}
classNames={{
enter: "animate__animated",
enterActive: "animate__fadeInUp",
exit: "animate__animated",
exitActive: "animate__backOutDown"
}}
>
<ListItem className={clsx(ss.todoItem, todo.completed && 'completed')}
secondaryAction={
<IconButton edge="end" onClick={() => dispatch(act.rmvItem(todo.id))}>
<ClearIcon />
</IconButton>
}
>
<ListItemIcon onClick={() => dispatch(act.toggleItem(todo.id))}>
{todo.completed ? <DoneIcon color="success" /> : <UndoIcon />}
</ListItemIcon>
<ListItemText>
{todo.text}
</ListItemText>
</ListItem>
</CSSTransition>
))}
</TransitionGroup>
</List>
<Stack direction="row" justifyContent="space-between" alignItems="baseline">
<P1>{todoActiveCount} items left</P1>
<RadioField value={filterCond} onChange={setFilterCond} />
<Button variant="text" onClick={() => dispatch(act.clearCompleted())}>Clear completed</Button>
</Stack>
</Paper>
</Container >
)
}
//=============================================================================
// const StyledListItem = styled(ListItem)(({ theme }) => ({
// // stripe, 產生條紋
// '&:nth-of-type(odd)': {
// backgroundColor: theme.palette.grey[100],
// },
// '&:hover': {
// backgroundColor: theme.palette.action.focus
// },
// }));
Last updated