Skip to content
Snippets Groups Projects
Unverified Commit 58e6f773 authored by Justin Coyne's avatar Justin Coyne Committed by GitHub
Browse files

Merge pull request #3851 from ProjectMirador/mui5-search

Pull more styling up
parents c03eb24c 752d5fc3
No related branches found
No related tags found
2 merge requests!19Draft: Merge video support into mui5,!18Only nudge over badge content for the WindowListButton; it needs special...
......@@ -67,14 +67,13 @@ describe('SearchHit', () => {
it('renders the annotationLabel if present', () => {
render(<Subject annotationLabel="The Anno Label" />);
expect(screen.getAllByRole('heading', { level: 6 })).toHaveLength(2);
expect(screen.getByRole('heading', { level: 6, name: 'The Anno Label' })).toHaveClass('MuiTypography-subtitle2');
expect(screen.getByRole('heading', { level: 4, name: 'The Anno Label' })).toBeInTheDocument();
});
it('does not render the typography if no annotation label is present', () => {
render(<Subject />);
expect(screen.getByRole('heading', { level: 6 })).toBeInTheDocument();
expect(screen.getByRole('heading', { level: 4 })).toBeInTheDocument();
});
});
......
......@@ -95,8 +95,8 @@ describe('SearchResults', () => {
searchHits: [],
});
expect(screen.getByRole('heading', { level: 6, name: 'The Anno Label' })).toBeInTheDocument();
expect(screen.getByRole('heading', { level: 6, name: 'Annother Anno Label' })).toBeInTheDocument();
expect(screen.getByRole('heading', { level: 4, name: 'The Anno Label' })).toBeInTheDocument();
expect(screen.getByRole('heading', { level: 4, name: 'Annother Anno Label' })).toBeInTheDocument();
});
});
......
......@@ -10,12 +10,27 @@ import { Img } from 'react-image';
import ManifestListItemError from '../containers/ManifestListItemError';
import ns from '../config/css-ns';
const StyledThumbnail = styled(Img)(({ theme }) => ({
const Root = styled(ListItem, { name: 'ManifestListItem', slot: 'root' })(({ ownerState, theme }) => ({
'&:hover,&:focus-within': {
backgroundColor: theme.palette.action.hover,
borderLeftColor: ownerState?.active ? theme.palette.primary.main : theme.palette.action.hover,
},
borderLeft: '4px solid',
borderLeftColor: ownerState?.active ? theme.palette.primary.main : 'transparent',
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
[theme.breakpoints.up('sm')]: {
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3),
},
}));
const StyledThumbnail = styled(Img, { name: 'ManifestListItem', slot: 'thumbnail' })(({ theme }) => ({
maxWidth: '100%',
objectFit: 'contain',
}));
const StyledLogo = styled(Img)(({ theme }) => ({
const StyledLogo = styled(Img, { name: 'ManifestListItem', slot: 'logo' })(({ theme }) => ({
height: '2.5rem',
maxWidth: '100%',
objectFit: 'contain',
......@@ -97,50 +112,23 @@ export class ManifestListItem extends Component {
if (error) {
return (
<ListItem
<Root
ownerState={this.props}
divider
selected={active}
className={active ? 'active' : ''}
sx={theme => ({
'&:hover,&:focus-within': {
backgroundColor: theme.palette.action.hover,
borderLeftColor: active ? theme.palette.action.hover : theme.palette.primary.main,
},
borderLeft: '4px solid',
borderLeftColor: active ? 'transparent' : theme.palette.primary.main,
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
[theme.breakpoints.up('sm')]: {
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3),
},
})}
data-manifestid={manifestId}
>
<ManifestListItemError manifestId={manifestId} />
</ListItem>
</Root>
);
}
return (
<ListItem
<Root
divider
selected={active}
className={active ? 'active' : ''}
sx={theme => ({
'&:hover,&:focus-within': {
backgroundColor: theme.palette.action.hover,
borderLeftColor: active ? theme.palette.primary.main : theme.palette.action.hover,
},
borderLeft: '4px solid',
borderLeftColor: active ? theme.palette.primary.main : 'transparent',
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
[theme.breakpoints.up('sm')]: {
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3),
},
})}
data-manifestid={manifestId}
data-active={active}
>
......@@ -224,7 +212,7 @@ export class ManifestListItem extends Component {
) : (
placeholder
)}
</ListItem>
</Root>
);
}
}
......
import PropTypes from 'prop-types';
import DialogContent from '@mui/material/DialogContent';
import { styled } from '@mui/material/styles';
/**
* ScrollIndicatedDialogContent ~ Inject a style into the DialogContent component
* to indicate there is scrollable content
*/
export function ScrollIndicatedDialogContent(props) {
const { classes, className, ...otherProps } = props;
const ourClassName = [className, classes.shadowScrollDialog].join(' ');
return (
<DialogContent
sx={{
const Root = styled(DialogContent, { name: 'ScrollIndicatedDialogContent', slot: 'root' })(({ theme }) => ({
/* Shadow covers */
background: `linear-gradient(${'background.paper'} 30%, rgba(255, 255, 255, 0)), `
+ `linear-gradient(rgba(255, 255, 255, 0), ${'background.paper'} 70%) 0 100%, `
background: `linear-gradient(${theme.palette.background.paper} 30%, rgba(255, 255, 255, 0)), `
+ `linear-gradient(rgba(255, 255, 255, 0), ${theme.palette.background.paper} 70%) 0 100%, `
// Shaddows
+ 'radial-gradient(50% 0, farthest-side, rgba(0, 0, 0, .2), rgba(0, 0, 0, 0)), '
+ 'radial-gradient(50% 100%, farthest-side, rgba(0, 0, 0, .2), rgba(0, 0, 0, 0)) 0 100%,',
/* Shadow covers */
background: `linear-gradient(${'background.paper'} 30%, rgba(255, 255, 255, 0)), ` // eslint-disable-line no-dupe-keys
+ `linear-gradient(rgba(255, 255, 255, 0), ${'background.paper'} 70%) 0 100%, `
background: `linear-gradient(${theme.palette.background.paper} 30%, rgba(255, 255, 255, 0)), ` // eslint-disable-line no-dupe-keys
+ `linear-gradient(rgba(255, 255, 255, 0), ${theme.palette.background.paper} 70%) 0 100%, `
// Shaddows
+ 'radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, .2), rgba(0, 0, 0, 0)), '
+ 'radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, .2), rgba(0, 0, 0, 0)) 0 100%;',
......@@ -29,7 +20,18 @@ export function ScrollIndicatedDialogContent(props) {
backgroundRepeat: 'no-repeat',
backgroundSize: '100% 40px, 100% 40px, 100% 14px, 100% 14px',
overflowY: 'auto',
}}
}));
/**
* ScrollIndicatedDialogContent ~ Inject a style into the DialogContent component
* to indicate there is scrollable content
*/
export function ScrollIndicatedDialogContent(props) {
const { classes, className, ...otherProps } = props;
const ourClassName = [className, classes.shadowScrollDialog].join(' ');
return (
<Root
className={ourClassName}
{...otherProps}
/>
......
......@@ -5,10 +5,45 @@ import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import Typography from '@mui/material/Typography';
import Chip from '@mui/material/Chip';
import { styled } from '@mui/material/styles';
import SanitizedHtml from '../containers/SanitizedHtml';
import TruncatedHit from '../lib/TruncatedHit';
import { ScrollTo } from './ScrollTo';
const Root = styled(ListItem, { name: 'SearchHit', slot: 'root' })(({ ownerState, theme }) => ({
'&.Mui-focused': {
'&:hover': {
...(ownerState.windowSelected && {
backgroundColor: 'inherit',
}),
},
...(ownerState.windowSelected && {
backgroundColor: 'inherit',
}),
},
paddingRight: theme.spacing(1),
}));
const CanvasLabel = styled('h4', { name: 'SearchHit', slot: 'canvasLabel' })(({ theme }) => ({
display: 'inline',
marginBottom: theme.spacing(1.5),
}));
const Counter = styled(Chip, { name: 'SearchHit', slot: 'counter' })(({ ownerState, theme }) => ({
// eslint-disable-next-line no-nested-ternary
backgroundColor: theme.palette.hitCounter.default,
...(ownerState.windowSelected && {
backgroundColor: theme.palette.highlights.primary,
}),
...(ownerState.adjacent && !ownerState.windowSelected && {
backgroundColor: theme.palette.highlights.secondary,
}),
height: 30,
marginRight: theme.spacing(1),
typography: 'subtitle2',
verticalAlign: 'inherit',
}));
/** */
export class SearchHit extends Component {
/** */
......@@ -93,6 +128,25 @@ export class SearchHit extends Component {
const truncatedHit = focused ? hit : hit && new TruncatedHit(hit, annotation);
const truncated = hit && (truncatedHit.before !== hit.before || truncatedHit.after !== hit.after);
const canvasLabelHtmlId = `${companionWindowId}-${index}`;
const ownerState = {
adjacent, focused, selected, windowSelected,
};
const header = (
<>
<Counter
component="span"
ownerState={ownerState}
label={index + 1}
/>
<CanvasLabel id={canvasLabelHtmlId}>
{canvasLabel}
{annotationLabel && (
<Typography component="span" sx={{ display: 'block', marginTop: 1 }}>{annotationLabel}</Typography>
)}
</CanvasLabel>
</>
);
return (
<ScrollTo
......@@ -100,50 +154,21 @@ export class SearchHit extends Component {
offsetTop={96} // offset for the height of the form above
scrollTo={windowSelected && !focused}
>
<ListItem
<Root
ownerState={ownerState}
className={windowSelected ? 'windowSelected' : ''}
sx={{
'&.Mui-focused': {
'&:hover': {
...(windowSelected && {
backgroundColor: 'inherit',
}),
},
...(windowSelected && {
backgroundColor: 'inherit',
}),
},
borderBottom: '0.5px solid',
borderBottomColor: 'divider',
paddingRight: 1,
}}
divider
button={!selected}
component="li"
onClick={this.handleClick}
selected={selected}
>
<ListItemText primaryTypographyProps={{ variant: 'body1' }}>
<Typography variant="subtitle2" sx={{ marginBottom: 1.5 }}>
<Chip
component="span"
label={index + 1}
sx={{
// eslint-disable-next-line no-nested-ternary
backgroundColor: windowSelected ? 'highlights.primary' : adjacent ? 'highlights.secondary' : 'hitCounter.default',
height: 30,
marginRight: 1,
typography: 'subtitle2',
verticalAlign: 'inherit',
}}
/>
<span id={canvasLabelHtmlId}>
{canvasLabel}
</span>
</Typography>
{annotationLabel && (
<Typography variant="subtitle2">{annotationLabel}</Typography>
)}
<ListItemText
primary={header}
primaryTypographyProps={{ component: 'div', sx: { marginBottom: 1 }, variant: 'subtitle2' }}
secondaryTypographyProps={{ variant: 'body1' }}
secondary={(
<>
{hit && (
<>
<SanitizedHtml ruleSet="iiif" htmlString={truncatedHit.before} />
......@@ -175,8 +200,10 @@ export class SearchHit extends Component {
</>
)}
{!hit && annotation && <SanitizedHtml ruleSet="iiif" htmlString={annotation.chars} />}
</ListItemText>
</ListItem>
</>
)}
/>
</Root>
</ScrollTo>
);
}
......
......@@ -64,17 +64,9 @@ export class SearchPanel extends Component {
/>
{
fetchSearch && suggestedSearches && query === '' && suggestedSearches.map(search => (
<Typography component="p" key={search} variant="body1">
<Typography component="p" key={search} variant="body1" sx={{ margin: 2 }}>
<Button
sx={{
'& span': {
lineHeight: '1.5em',
},
margin: 2,
padding: 0,
textAlign: 'inherit',
textTransform: 'none',
}}
variant="inlineText"
color="secondary"
onClick={() => fetchSearch(`${searchService.id}?q=${search}`, search)}
>
......
......@@ -7,15 +7,17 @@ import isObject from 'lodash/isObject';
import Autocomplete from '@mui/material/Autocomplete';
import CircularProgress from '@mui/material/CircularProgress';
import TextField from '@mui/material/TextField';
import InputAdornment from '@mui/material/InputAdornment';
import SearchIcon from '@mui/icons-material/SearchSharp';
import MiradorMenuButton from '../containers/MiradorMenuButton';
import SearchPanelNavigation from '../containers/SearchPanelNavigation';
const StyledForm = styled('form')({
});
const StyledForm = styled('form', { name: 'SearchPanelControls', slot: 'form' })(({ theme }) => ({
paddingBottom: theme.spacing(1),
paddingRight: theme.spacing(1.5),
width: '100%',
}));
const StyledEndAdornment = styled('div')({
});
/** Sometimes an autocomplete match can be a simple string, other times an object
with a `match` property, this function abstracts that away */
const getMatch = (option) => (isObject(option) ? option.match : option);
......@@ -131,11 +133,6 @@ export class SearchPanelControls extends Component {
return (
<>
<StyledForm
sx={{
paddingBottom: 1,
paddingRight: 1.5,
width: '100%',
}}
aria-label={t('searchTitle')}
onSubmit={this.submitSearch}
>
......@@ -152,6 +149,7 @@ export class SearchPanelControls extends Component {
onChange={this.selectItem}
onInputChange={this.handleChange}
freeSolo
disableClearable
renderInput={params => (
<TextField
{...params}
......@@ -160,24 +158,23 @@ export class SearchPanelControls extends Component {
InputProps={{
...params.InputProps,
endAdornment: (
<StyledEndAdornment sx={{
position: 'absolute',
right: 0,
}}
>
<InputAdornment sx={{ position: 'relative' }} position="end">
<MiradorMenuButton aria-label={t('searchSubmitAria')} type="submit">
<SearchIcon />
</MiradorMenuButton>
{Boolean(searchIsFetching) && (
<CircularProgress
sx={{
left: '50%',
marginLeft: '-25px',
marginTop: '-25px',
position: 'absolute',
right: 0,
top: '50%',
}}
size={50}
/>
)}
</StyledEndAdornment>
</InputAdornment>
),
}}
/>
......
......@@ -324,6 +324,20 @@ export default {
},
],
},
MuiButton: {
styleOverrides: {
inlineText: {
lineHeight: '1.5em',
padding: 0,
textAlign: 'inherit',
textTransform: 'none',
},
inlineTextSecondary: ({ theme }) => ({
color: theme.palette.secondary.main,
}),
}
},
MuiButtonBase: {
defaultProps: {
disableTouchRipple: true,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment