diff --git a/README.md b/README.md index 3799630..68b5929 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/docs/bundle.js b/docs/bundle.js index ebb34d7..f46bb73 100644 --- a/docs/bundle.js +++ b/docs/bundle.js @@ -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 diff --git a/docs/index.js b/docs/index.js index b647af5..625924a 100644 --- a/docs/index.js +++ b/docs/index.js @@ -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 {
Theme:  - - - + {['auto', 'light', 'dark'].map((theme) => { + return ( + + ) + })}
diff --git a/src/components/picker/nimble-picker.js b/src/components/picker/nimble-picker.js index 6f5fe89..f2a559e 100644 --- a/src/components/picker/nimble-picker.js +++ b/src/components/picker/nimble-picker.js @@ -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 (
diff --git a/src/utils/shared-default-props.js b/src/utils/shared-default-props.js index e9f9961..ea369dc 100644 --- a/src/utils/shared-default-props.js +++ b/src/utils/shared-default-props.js @@ -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, diff --git a/src/utils/shared-props.js b/src/utils/shared-props.js index f006407..8c8faa1 100644 --- a/src/utils/shared-props.js +++ b/src/utils/shared-props.js @@ -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), diff --git a/stories/index.js b/stories/index.js index 32e7e93..954fd51 100644 --- a/stories/index.js +++ b/stories/index.js @@ -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)}