Media Gallery: Support for dark theme

This commit is contained in:
Scott Nonnenberg 2018-07-17 17:15:34 -07:00
parent 3c69886320
commit 7e2d7b5e60
10 changed files with 236 additions and 190 deletions

View file

@ -1682,6 +1682,147 @@
border-radius: 4px;
}
// Module: Media Gallery
.module-media-gallery {
display: flex;
flex-direction: column;
flex-grow: 1;
width: 100%;
height: 100%;
}
.module-media-gallery__tab-container {
display: flex;
flex-grow: 0;
flex-shrink: 0;
cursor: pointer;
width: 100%;
}
.module-media-gallery__tab {
width: 100%;
background-color: $color-light-02;
padding: 20px;
text-align: center;
}
.module-media-gallery__tab--active {
border-bottom: 2px solid $color-signal-blue;
}
.module-media-gallery__content {
display: flex;
flex-grow: 1;
overflow-y: auto;
padding: 20px;
}
.module-media-gallery__sections {
display: flex;
flex-grow: 1;
flex-direction: column;
}
// Module: Attachment Section
.module-attachment-section {
width: 100%;
}
.module-attachment-section__header {
font-size: 14px;
font-weight: normal;
line-height: 28px;
}
.module-attachment-section__items {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
}
// Module: Document List Item
.module-document-list-item {
width: 100%;
height: 72px;
}
.module-document-list-item--with-separator {
border-bottom: 1px solid $color-light-02;
}
.module-document-list-item__content {
cursor: pointer;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
height: 100%;
}
.module-document-list-item__icon {
flex-shrink: 0;
width: 48px;
height: 48px;
@include color-svg('../images/file.svg', $color-light-35);
}
.module-document-list-item__metadata {
display: inline-flex;
flex-direction: column;
flex-grow: 1;
flex-shrink: 0;
margin-left: 8px;
margin-right: 8px;
}
.module-document-list-item__file-name {
font-weight: bold;
}
.module-document-list-item__file-size {
display: inline-block;
margin-top: 8px;
font-size: 80%;
}
.module-document-list-item__date {
display: inline-block;
flex-shrink: 0;
}
// Module: Media Grid Item
.module-media-grid-item {
height: 94px;
width: 94px;
cursor: pointer;
background-color: $color-light-10;
margin-right: 4px;
margin-bottom: 4px;
}
.module-media-grid-item__image {
height: 94px;
width: 94px;
object-fit: cover;
}
// Module: Empty State
.module-empty-state {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
font-size: 28px;
color: $color-light-45;
}
// Third-party module: react-contextmenu
.react-contextmenu {

View file

@ -1263,6 +1263,38 @@ body.dark-theme {
background-color: $color-core-red;
}
// Module: Media Gallery
.module-media-gallery__tab {
background-color: $color-dark-85;
}
.module-media-gallery__tab--active {
border-bottom: 2px solid $color-signal-blue;
}
// Module: Document List Item
.module-document-list-item--with-separator {
border-bottom: 1px solid $color-dark-70;
}
.module-document-list-item__icon {
@include color-svg('../images/file.svg', $color-dark-60);
}
// Module: Media Grid Item
.module-media-grid-item {
background-color: $color-dark-85;
}
// Module: Empty State
.module-empty-state {
color: $color-dark-55;
}
// Third-party module: react-contextmenu
.react-contextmenu {

View file

@ -5,13 +5,24 @@ import React from 'react';
import classNames from 'classnames';
import is from '@sindresorhus/is';
import * as Colors from './styles/Colors';
import * as GoogleChrome from '../util/GoogleChrome';
import * as MIME from '../types/MIME';
import { colorSVG } from '../styles/colorSVG';
import { Localizer } from '../types/Util';
const Colors = {
TEXT_SECONDARY: '#bbb',
ICON_SECONDARY: '#ccc',
};
const colorSVG = (url: string, color: string) => {
return {
WebkitMask: `url(${url}) no-repeat center`,
WebkitMaskSize: '100%',
backgroundColor: color,
};
};
interface Props {
close: () => void;
contentType: MIME.MIMEType | undefined;

View file

@ -6,27 +6,10 @@ import { ItemClickEvent } from './types/ItemClickEvent';
import { MediaGridItem } from './MediaGridItem';
import { Message } from './types/Message';
import { missingCaseError } from '../../../util/missingCaseError';
const styles = {
container: {
width: '100%',
},
header: {
fontSize: 14,
fontWeight: 'normal',
lineHeight: '28px',
} as React.CSSProperties,
itemContainer: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-start',
alignItems: 'flex-start',
} as React.CSSProperties,
};
import { Localizer } from '../../../types/Util';
interface Props {
i18n: (value: string) => string;
i18n: Localizer;
header?: string;
type: AttachmentType;
messages: Array<Message>;
@ -38,9 +21,11 @@ export class AttachmentSection extends React.Component<Props> {
const { header } = this.props;
return (
<div style={styles.container}>
<h2 style={styles.header}>{header}</h2>
<div style={styles.itemContainer}>{this.renderItems()}</div>
<div className="module-attachment-section">
<h2 className="module-attachment-section__header">{header}</h2>
<div className="module-attachment-section__items">
{this.renderItems()}
</div>
</div>
);
}
@ -61,6 +46,7 @@ export class AttachmentSection extends React.Component<Props> {
key={message.id}
message={message}
onClick={onClick}
i18n={i18n}
/>
);
case 'documents':
@ -69,7 +55,6 @@ export class AttachmentSection extends React.Component<Props> {
key={message.id}
fileName={firstAttachment.fileName}
fileSize={firstAttachment.size}
i18n={i18n}
shouldShowSeparator={shouldShowSeparator}
onClick={onClick}
timestamp={message.received_at}

View file

@ -1,14 +1,12 @@
import React from 'react';
import classNames from 'classnames';
import moment from 'moment';
// tslint:disable-next-line:match-default-export-name
import formatFileSize from 'filesize';
import { Localizer } from '../../../types/Util';
interface Props {
// Required
i18n: Localizer;
timestamp: number;
// Optional
@ -18,49 +16,6 @@ interface Props {
shouldShowSeparator?: boolean;
}
const styles = {
container: {
width: '100%',
height: 72,
},
containerSeparator: {
borderBottomWidth: 1,
borderBottomColor: '#ccc',
borderBottomStyle: 'solid',
},
itemContainer: {
cursor: 'pointer',
display: 'flex',
flexDirection: 'row',
flexWrap: 'nowrap',
alignItems: 'center',
height: '100%',
} as React.CSSProperties,
itemMetadata: {
display: 'inline-flex',
flexDirection: 'column',
flexGrow: 1,
flexShrink: 0,
marginLeft: 8,
marginRight: 8,
} as React.CSSProperties,
itemDate: {
display: 'inline-block',
flexShrink: 0,
},
itemIcon: {
flexShrink: 0,
},
itemFileName: {
fontWeight: 'bold',
} as React.CSSProperties,
itemFileSize: {
display: 'inline-block',
marginTop: 8,
fontSize: '80%',
},
};
export class DocumentListItem extends React.Component<Props> {
public static defaultProps: Partial<Props> = {
shouldShowSeparator: true,
@ -71,10 +26,12 @@ export class DocumentListItem extends React.Component<Props> {
return (
<div
style={{
...styles.container,
...(shouldShowSeparator ? styles.containerSeparator : {}),
}}
className={classNames(
'module-document-list-item',
shouldShowSeparator
? 'module-document-list-item--with-separator'
: null
)}
>
{this.renderContent()}
</div>
@ -82,28 +39,24 @@ export class DocumentListItem extends React.Component<Props> {
}
private renderContent() {
const { fileName, fileSize, timestamp, i18n } = this.props;
const { fileName, fileSize, timestamp } = this.props;
return (
<div
style={styles.itemContainer}
className="module-document-list-item__content"
role="button"
onClick={this.props.onClick}
>
<img
alt={i18n('fileIconAlt')}
src="images/file.svg"
width="48"
height="48"
style={styles.itemIcon}
/>
<div style={styles.itemMetadata}>
<span style={styles.itemFileName}>{fileName}</span>
<span style={styles.itemFileSize}>
<div className="module-document-list-item__icon" />
<div className="module-document-list-item__metadata">
<span className="module-document-list-item__file-name">
{fileName}
</span>
<span className="module-document-list-item__file-size">
{typeof fileSize === 'number' ? formatFileSize(fileSize) : ''}
</span>
</div>
<div style={styles.itemDate}>
<div className="module-document-list-item__date">
{moment(timestamp).format('ddd, MMM D, Y')}
</div>
</div>

View file

@ -3,28 +3,14 @@
*/
import React from 'react';
import * as Colors from '../../styles/Colors';
interface Props {
label: string;
}
const styles = {
container: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexGrow: 1,
fontSize: 28,
color: Colors.TEXT_SECONDARY,
} as React.CSSProperties,
};
export class EmptyState extends React.Component<Props> {
public render() {
const { label } = this.props;
return <div style={styles.container}>{label}</div>;
return <div className="module-empty-state">{label}</div>;
}
}

View file

@ -1,4 +1,5 @@
import React from 'react';
import classNames from 'classnames';
import moment from 'moment';
@ -9,10 +10,11 @@ import { groupMessagesByDate } from './groupMessagesByDate';
import { ItemClickEvent } from './types/ItemClickEvent';
import { Message } from './types/Message';
import { missingCaseError } from '../../../util/missingCaseError';
import { Localizer } from '../../../types/Util';
interface Props {
documents: Array<Message>;
i18n: (key: string, values?: Array<string>) => string;
i18n: Localizer;
media: Array<Message>;
onItemClick?: (event: ItemClickEvent) => void;
}
@ -22,49 +24,6 @@ interface State {
}
const MONTH_FORMAT = 'MMMM YYYY';
const COLOR_GRAY = '#f3f3f3';
const tabStyle = {
width: '100%',
backgroundColor: COLOR_GRAY,
padding: 20,
textAlign: 'center',
};
const styles = {
container: {
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
width: '100%',
height: '100%',
} as React.CSSProperties,
tabContainer: {
display: 'flex',
flexGrow: 0,
flexShrink: 0,
cursor: 'pointer',
width: '100%',
},
tab: {
default: tabStyle,
active: {
...tabStyle,
borderBottom: '2px solid #08f',
},
},
contentContainer: {
display: 'flex',
flexGrow: 1,
overflowY: 'auto',
padding: 20,
} as React.CSSProperties,
sectionContainer: {
display: 'flex',
flexGrow: 1,
flexDirection: 'column',
} as React.CSSProperties,
};
interface TabSelectEvent {
type: AttachmentType;
@ -89,7 +48,10 @@ const Tab = ({
return (
<div
style={isSelected ? styles.tab.active : styles.tab.default}
className={classNames(
'module-media-gallery__tab',
isSelected ? 'module-media-gallery__tab--active' : null
)}
onClick={handleClick}
role="tab"
>
@ -107,8 +69,8 @@ export class MediaGallery extends React.Component<Props, State> {
const { selectedTab } = this.state;
return (
<div style={styles.container}>
<div style={styles.tabContainer}>
<div className="module-media-gallery">
<div className="module-media-gallery__tab-container">
<Tab
label="Media"
type="media"
@ -122,7 +84,9 @@ export class MediaGallery extends React.Component<Props, State> {
onSelect={this.handleTabSelect}
/>
</div>
<div style={styles.contentContainer}>{this.renderSections()}</div>
<div className="module-media-gallery__content">
{this.renderSections()}
</div>
</div>
);
}
@ -176,6 +140,6 @@ export class MediaGallery extends React.Component<Props, State> {
);
});
return <div style={styles.sectionContainer}>{sections}</div>;
return <div className="module-media-gallery__sections">{sections}</div>;
}
}

View file

@ -1,52 +1,38 @@
import React from 'react';
import { Message } from './types/Message';
import { Localizer } from '../../../types/Util';
interface Props {
message: Message;
onClick?: () => void;
i18n: Localizer;
}
const size = {
width: 94,
height: 94,
};
const styles = {
container: {
...size,
cursor: 'pointer',
backgroundColor: '#f3f3f3',
marginRight: 4,
marginBottom: 4,
},
image: {
...size,
backgroundSize: 'cover',
},
};
export class MediaGridItem extends React.Component<Props> {
public renderContent() {
const { message } = this.props;
const { message, i18n } = this.props;
if (!message.objectURL) {
return null;
}
return (
<div
style={{
...styles.container,
...styles.image,
backgroundImage: `url("${message.objectURL}")`,
}}
<img
alt={i18n('lightboxImageAlt')}
className="module-media-grid-item__image"
src={message.objectURL}
/>
);
}
public render() {
return (
<div style={styles.container} role="button" onClick={this.props.onClick}>
<div
className="module-media-grid-item"
role="button"
onClick={this.props.onClick}
>
{this.renderContent()}
</div>
);

View file

@ -1,5 +0,0 @@
/**
* @prettier
*/
export const TEXT_SECONDARY = '#bbb';
export const ICON_SECONDARY = '#ccc';

View file

@ -1,7 +0,0 @@
export const colorSVG = (url: string, color: string) => {
return {
WebkitMask: `url(${url}) no-repeat center`,
WebkitMaskSize: '100%',
backgroundColor: color,
};
};