f238c43307
* fix: replace feather icons with remix icons on swarm button * fix: remove feather icons package (#415) * fix: remove all feather icons and replace with remix icons * fix: few stray weird icons
166 lines
5.3 KiB
TypeScript
166 lines
5.3 KiB
TypeScript
import { Grid, IconButton, InputBase, ListItem, Typography } from '@material-ui/core'
|
|
import Collapse from '@material-ui/core/Collapse'
|
|
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
|
|
import { ChangeEvent, ReactElement, useState } from 'react'
|
|
import Check from 'remixicon-react/CheckLineIcon'
|
|
import Edit from 'remixicon-react/PencilLineIcon'
|
|
import Minus from 'remixicon-react/SubtractLineIcon'
|
|
import X from 'remixicon-react/CloseLineIcon'
|
|
import ExpandableListItemActions from './ExpandableListItemActions'
|
|
import ExpandableListItemNote from './ExpandableListItemNote'
|
|
import { SwarmButton } from './SwarmButton'
|
|
import type { RemixiconReactIconProps } from 'remixicon-react'
|
|
|
|
const useStyles = makeStyles((theme: Theme) =>
|
|
createStyles({
|
|
header: {
|
|
backgroundColor: theme.palette.background.paper,
|
|
marginBottom: theme.spacing(0.25),
|
|
borderLeft: `${theme.spacing(0.25)}px solid rgba(0,0,0,0)`,
|
|
wordBreak: 'break-word',
|
|
},
|
|
headerOpen: {
|
|
borderLeft: `${theme.spacing(0.25)}px solid ${theme.palette.primary.main}`,
|
|
},
|
|
copyValue: {
|
|
cursor: 'pointer',
|
|
padding: theme.spacing(1),
|
|
borderRadius: 0,
|
|
'&:hover': {
|
|
backgroundColor: '#fcf2e8',
|
|
color: theme.palette.primary.main,
|
|
},
|
|
},
|
|
content: {
|
|
marginTop: theme.spacing(1),
|
|
marginBottom: theme.spacing(1),
|
|
},
|
|
keyMargin: {
|
|
marginRight: theme.spacing(1),
|
|
},
|
|
unselectableLabel: {
|
|
cursor: 'default',
|
|
userSelect: 'none',
|
|
// Many browsers don't support yet the general user-select css property
|
|
WebkitUserSelect: 'none',
|
|
MozUserSelect: 'none',
|
|
msUserSelect: 'none',
|
|
},
|
|
}),
|
|
)
|
|
|
|
interface Props {
|
|
label: string
|
|
value?: string
|
|
placeholder?: string
|
|
helperText?: string
|
|
expandedOnly?: boolean
|
|
confirmLabel?: string
|
|
confirmLabelDisabled?: boolean
|
|
confirmIcon?: React.ComponentType<RemixiconReactIconProps>
|
|
loading?: boolean
|
|
onChange?: (value: string) => void
|
|
onConfirm?: (value: string) => void
|
|
mapperFn?: (value: string) => string
|
|
locked?: boolean
|
|
}
|
|
|
|
export default function ExpandableListItemKey({
|
|
label,
|
|
value,
|
|
onConfirm,
|
|
onChange,
|
|
confirmLabel,
|
|
confirmLabelDisabled,
|
|
confirmIcon,
|
|
expandedOnly,
|
|
helperText,
|
|
placeholder,
|
|
loading,
|
|
mapperFn,
|
|
locked,
|
|
}: Props): ReactElement | null {
|
|
const classes = useStyles()
|
|
const [open, setOpen] = useState(Boolean(expandedOnly))
|
|
const [inputValue, setInputValue] = useState<string>(value || '')
|
|
const toggleOpen = () => setOpen(!open)
|
|
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
if (mapperFn) {
|
|
e.target.value = mapperFn(e.target.value)
|
|
}
|
|
|
|
setInputValue(e.target.value)
|
|
|
|
if (onChange) onChange(e.target.value)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<ListItem className={`${classes.header} ${open ? classes.headerOpen : ''}`}>
|
|
<Grid container direction="column" justifyContent="space-between" alignItems="stretch">
|
|
<Grid container direction="row" justifyContent="space-between" alignItems="center">
|
|
{label && (
|
|
<Typography variant="body1" className={classes.unselectableLabel}>
|
|
{label}
|
|
</Typography>
|
|
)}
|
|
<Typography variant="body2">
|
|
<div>
|
|
{!open && value}
|
|
{!expandedOnly && !locked && (
|
|
<IconButton size="small" className={classes.copyValue}>
|
|
{open ? (
|
|
<Minus onClick={toggleOpen} strokeWidth={1} />
|
|
) : (
|
|
<Edit onClick={toggleOpen} strokeWidth={1} />
|
|
)}
|
|
</IconButton>
|
|
)}
|
|
</div>
|
|
</Typography>
|
|
</Grid>
|
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
|
<InputBase
|
|
value={inputValue}
|
|
placeholder={placeholder}
|
|
onChange={handleChange}
|
|
fullWidth
|
|
className={classes.content}
|
|
autoFocus
|
|
hidden={locked}
|
|
/>
|
|
</Collapse>
|
|
</Grid>
|
|
</ListItem>
|
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
|
{helperText && <ExpandableListItemNote>{helperText}</ExpandableListItemNote>}
|
|
<ExpandableListItemActions>
|
|
<SwarmButton
|
|
disabled={
|
|
loading ||
|
|
inputValue === value ||
|
|
Boolean(confirmLabelDisabled) || // Disable if external validation is provided
|
|
(inputValue === '' && value === undefined) // Disable if no initial value was not provided and the field is empty. The undefined check is improtant so that it is possible to submit with empty input in other cases
|
|
}
|
|
loading={loading}
|
|
iconType={confirmIcon ?? Check}
|
|
onClick={() => {
|
|
if (onConfirm) onConfirm(inputValue)
|
|
}}
|
|
>
|
|
{confirmLabel || 'Save'}
|
|
</SwarmButton>
|
|
<SwarmButton
|
|
disabled={loading || inputValue === value || inputValue === ''}
|
|
iconType={X}
|
|
onClick={() => setInputValue(value || '')}
|
|
cancel
|
|
>
|
|
Cancel
|
|
</SwarmButton>
|
|
</ExpandableListItemActions>
|
|
</Collapse>
|
|
</>
|
|
)
|
|
}
|