Update theme handling [Fix #404]

- Defaults to “light”
- “auto” picks light/dark based on `prefers-color-scheme: dark` media query (and actually updates automatically)
release
Etienne Lemay 2020-03-16 13:45:59 -04:00
parent c41fc5ec7a
commit d1138c00e8
No known key found for this signature in database
GPG Key ID: EE7CF89146BB28E9
7 changed files with 90 additions and 59 deletions

View File

@ -29,7 +29,6 @@ import { Picker } from 'emoji-mart'
| **autoFocus** | | `false` | Auto focus the search input when mounted |
| **color** | | `#ae65c5` | The top bar anchors select and hover color |
| **emoji** | | `department_store` | The emoji shown when no emojis are hovered, set to an empty string to show nothing |
| **darkMode** | | varies | Dark mode (boolean). `true` by default if the browser reports [`prefers-color-scheme: dark`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme). |
| **include** | | `[]` | Only load included categories. Accepts [I18n categories keys](#i18n). Order will be respected, except for the `recent` category which will always be the first. |
| **exclude** | | `[]` | Don't load excluded categories. Accepts [I18n categories keys](#i18n). |
| **custom** | | `[]` | [Custom emojis](#custom-emojis) |
@ -43,6 +42,7 @@ import { Picker } from 'emoji-mart'
| **i18n** | | [`{…}`](#i18n) | [An object](#i18n) containing localized strings |
| **native** | | `false` | Renders the native unicode emoji |
| **set** | | `apple` | The emoji set: `'apple', 'google', 'twitter', 'facebook'` |
| **theme** | | `light` | The picker theme: `'auto', 'light', 'dark'` |
| **sheetSize** | | `64` | The emoji [sheet size](#sheet-sizes): `16, 20, 32, 64` |
| **backgroundImageFn** | | ```((set, sheetSize) => …)``` | A Fn that returns that image sheet to use for emojis. Useful for avoiding a request if you have the sheet locally. |
| **emojisToShowFilter** | | ```((emoji) => true)``` | A Fn to choose whether an emoji should be displayed or not |

View File

@ -1539,6 +1539,7 @@ var PickerPropTypes = {
showSkinTones: _propTypes["default"].bool,
emojiTooltip: EmojiPropTypes.tooltip,
useButton: EmojiPropTypes.useButton,
theme: _propTypes["default"].oneOf(['auto', 'light', 'dark']),
include: _propTypes["default"].arrayOf(_propTypes["default"].string),
exclude: _propTypes["default"].arrayOf(_propTypes["default"].string),
recent: _propTypes["default"].arrayOf(_propTypes["default"].string),
@ -1602,6 +1603,7 @@ var PickerDefaultProps = {
emoji: 'department_store',
color: '#ae65c5',
set: EmojiDefaultProps.set,
theme: 'light',
skin: null,
defaultSkin: EmojiDefaultProps.skin,
"native": EmojiDefaultProps["native"],
@ -1610,7 +1612,6 @@ var PickerDefaultProps = {
emojisToShowFilter: null,
showPreview: true,
showSkinTones: true,
darkMode: !!(typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches),
emojiTooltip: EmojiDefaultProps.tooltip,
useButton: EmojiDefaultProps.useButton,
autoFocus: false,
@ -2621,6 +2622,8 @@ function (_React$PureComponent) {
_this.setPreviewRef = _this.setPreviewRef.bind((0, _assertThisInitialized2["default"])(_this));
_this.handleSkinChange = _this.handleSkinChange.bind((0, _assertThisInitialized2["default"])(_this));
_this.handleKeyDown = _this.handleKeyDown.bind((0, _assertThisInitialized2["default"])(_this));
_this.handleDarkMatchMediaChange = _this.handleDarkMatchMediaChange.bind((0, _assertThisInitialized2["default"])(_this));
_this.state.theme = _this.getPreferredTheme();
return _this;
}
@ -2650,6 +2653,10 @@ function (_React$PureComponent) {
this.SEARCH_CATEGORY.emojis = null;
clearTimeout(this.leaveTimeout);
clearTimeout(this.firstRenderTimeout);
if (this.darkMatchMedia) {
this.darkMatchMedia.removeListener(this.handleDarkMatchMediaChange);
}
}
}, {
key: "testStickyPosition",
@ -2661,6 +2668,27 @@ function (_React$PureComponent) {
});
this.hasStickyPosition = !!stickyTestElement.style.position.length;
}
}, {
key: "getPreferredTheme",
value: function getPreferredTheme() {
if (this.props.theme != 'auto') return this.props.theme;
if (typeof matchMedia !== 'function') return _sharedDefaultProps.PickerDefaultProps.theme;
if (!this.darkMatchMedia) {
this.darkMatchMedia = matchMedia('(prefers-color-scheme: dark)');
this.darkMatchMedia.addListener(this.handleDarkMatchMediaChange);
}
if (this.darkMatchMedia.media.match(/^not/)) return _sharedDefaultProps.PickerDefaultProps.theme;
return this.darkMatchMedia.matches ? 'dark' : 'light';
}
}, {
key: "handleDarkMatchMediaChange",
value: function handleDarkMatchMediaChange() {
this.setState({
theme: this.getPreferredTheme()
});
}
}, {
key: "handleEmojiOver",
value: function handleEmojiOver(emoji) {
@ -2972,14 +3000,15 @@ function (_React$PureComponent) {
skinEmoji = _this$props.skinEmoji,
notFound = _this$props.notFound,
notFoundEmoji = _this$props.notFoundEmoji,
darkMode = _this$props.darkMode,
skin = this.state.skin,
_this$state = this.state,
skin = _this$state.skin,
theme = _this$state.theme,
width = perLine * (emojiSize + 12) + 12 + 2 + (0, _utils.measureScrollbar)();
return _react["default"].createElement("section", {
style: _objectSpread({
width: width
}, style),
className: "emoji-mart ".concat(darkMode ? 'emoji-mart-dark' : ''),
className: "emoji-mart emoji-mart-".concat(theme),
"aria-label": title,
onKeyDown: this.handleKeyDown
}, _react["default"].createElement("div", {
@ -3708,6 +3737,7 @@ class Example extends __WEBPACK_IMPORTED_MODULE_1_react___default.a.Component {
this.state = {
native: true,
set: 'apple',
theme: 'auto',
emoji: 'point_up',
title: 'Pick your emoji…',
custom: CUSTOM_EMOJIS,
@ -3747,28 +3777,17 @@ class Example extends __WEBPACK_IMPORTED_MODULE_1_react___default.a.Component {
}, props), set);
})), __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement("div", {
className: "row-small sets"
}, "Theme:\xA0", __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement("button", {
disabled: this.state.darkMode == undefined,
onClick: () => {
this.setState({
darkMode: undefined
});
}
}, "auto"), __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement("button", {
disabled: this.state.darkMode == false,
onClick: () => {
this.setState({
darkMode: false
});
}
}, "light"), __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement("button", {
disabled: this.state.darkMode,
onClick: () => {
this.setState({
darkMode: true
});
}
}, "dark")), __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement("div", {
}, "Theme:\xA0", ['auto', 'light', 'dark'].map(theme => {
return __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement("button", {
key: theme,
disabled: theme == this.state.theme,
onClick: () => {
this.setState({
theme
});
}
}, theme);
})), __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement("div", {
className: "row"
}, __WEBPACK_IMPORTED_MODULE_1_react___default.a.createElement(__WEBPACK_IMPORTED_MODULE_3__dist__["Picker"], __WEBPACK_IMPORTED_MODULE_0__babel_runtime_helpers_extends___default()({}, this.state, {
onSelect: console.log

View File

@ -42,6 +42,7 @@ class Example extends React.Component {
this.state = {
native: true,
set: 'apple',
theme: 'auto',
emoji: 'point_up',
title: 'Pick your emoji…',
custom: CUSTOM_EMOJIS,
@ -88,30 +89,19 @@ class Example extends React.Component {
<div className="row-small sets">
Theme: 
<button
disabled={this.state.darkMode == undefined}
onClick={() => {
this.setState({ darkMode: undefined })
}}
>
auto
</button>
<button
disabled={this.state.darkMode == false}
onClick={() => {
this.setState({ darkMode: false })
}}
>
light
</button>
<button
disabled={this.state.darkMode}
onClick={() => {
this.setState({ darkMode: true })
}}
>
dark
</button>
{['auto', 'light', 'dark'].map((theme) => {
return (
<button
key={theme}
disabled={theme == this.state.theme}
onClick={() => {
this.setState({ theme })
}}
>
{theme}
</button>
)
})}
</div>
<div className="row">

View File

@ -196,6 +196,9 @@ export default class NimblePicker extends React.PureComponent {
this.setPreviewRef = this.setPreviewRef.bind(this)
this.handleSkinChange = this.handleSkinChange.bind(this)
this.handleKeyDown = this.handleKeyDown.bind(this)
this.handleDarkMatchMediaChange = this.handleDarkMatchMediaChange.bind(this)
this.state.theme = this.getPreferredTheme()
}
static getDerivedStateFromProps(props, state) {
@ -232,6 +235,10 @@ export default class NimblePicker extends React.PureComponent {
clearTimeout(this.leaveTimeout)
clearTimeout(this.firstRenderTimeout)
if (this.darkMatchMedia) {
this.darkMatchMedia.removeListener(this.handleDarkMatchMediaChange)
}
}
testStickyPosition() {
@ -246,6 +253,23 @@ export default class NimblePicker extends React.PureComponent {
this.hasStickyPosition = !!stickyTestElement.style.position.length
}
getPreferredTheme() {
if (this.props.theme != 'auto') return this.props.theme
if (typeof matchMedia !== 'function') return PickerDefaultProps.theme
if (!this.darkMatchMedia) {
this.darkMatchMedia = matchMedia('(prefers-color-scheme: dark)')
this.darkMatchMedia.addListener(this.handleDarkMatchMediaChange)
}
if (this.darkMatchMedia.media.match(/^not/)) return PickerDefaultProps.theme
return this.darkMatchMedia.matches ? 'dark' : 'light'
}
handleDarkMatchMediaChange() {
this.setState({ theme: this.getPreferredTheme() })
}
handleEmojiOver(emoji) {
var { preview } = this
if (!preview) {
@ -529,15 +553,14 @@ export default class NimblePicker extends React.PureComponent {
skinEmoji,
notFound,
notFoundEmoji,
darkMode,
} = this.props,
{ skin } = this.state,
{ skin, theme } = this.state,
width = perLine * (emojiSize + 12) + 12 + 2 + measureScrollbar()
return (
<section
style={{ width: width, ...style }}
className={`emoji-mart ${darkMode ? 'emoji-mart-dark' : ''}`}
className={`emoji-mart emoji-mart-${theme}`}
aria-label={title}
onKeyDown={this.handleKeyDown}
>

View File

@ -24,6 +24,7 @@ const PickerDefaultProps = {
emoji: 'department_store',
color: '#ae65c5',
set: EmojiDefaultProps.set,
theme: 'light',
skin: null,
defaultSkin: EmojiDefaultProps.skin,
native: EmojiDefaultProps.native,
@ -32,10 +33,6 @@ const PickerDefaultProps = {
emojisToShowFilter: null,
showPreview: true,
showSkinTones: true,
darkMode: !!(
typeof matchMedia === 'function' &&
matchMedia('(prefers-color-scheme: dark)').matches
),
emojiTooltip: EmojiDefaultProps.tooltip,
useButton: EmojiDefaultProps.useButton,
autoFocus: false,

View File

@ -41,6 +41,7 @@ const PickerPropTypes = {
showSkinTones: PropTypes.bool,
emojiTooltip: EmojiPropTypes.tooltip,
useButton: EmojiPropTypes.useButton,
theme: PropTypes.oneOf(['auto', 'light', 'dark']),
include: PropTypes.arrayOf(PropTypes.string),
exclude: PropTypes.arrayOf(PropTypes.string),
recent: PropTypes.arrayOf(PropTypes.string),

View File

@ -21,6 +21,7 @@ import {
import data from '../data/all.json'
import '../css/emoji-mart.css'
const THEMES = ['auto', 'light', 'dark']
const SETS = ['apple', 'google', 'twitter', 'facebook']
const CUSTOM_EMOJIS = [
{
@ -51,7 +52,7 @@ storiesOf('Picker', module)
onSelect={action('selected')}
onSkinChange={action('skin changed')}
native={boolean('Unicode', true)}
darkMode={boolean('Dark mode', false)}
theme={select('Theme', THEMES, THEMES[0])}
set={select('Emoji pack', SETS, SETS[0])}
emojiSize={number('Emoji size', 24)}
perLine={number('Per line', 9)}