@sanity/rich-date-input 2.0.10-canary.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,82 @@
1
+ import {SearchIcon} from '@sanity/icons'
2
+ import {ObjectInputProps, set} from 'sanity'
3
+ import {allTimezones, unlocalizeDateTime} from '../utils'
4
+ import {NormalizedTimeZone, RichDate} from '../types'
5
+ import {Autocomplete, Card, Text, Box} from '@sanity/ui'
6
+ import {formatInTimeZone, zonedTimeToUtc} from 'date-fns-tz'
7
+
8
+ interface TimezoneSelectorProps {
9
+ onChange: Pick<ObjectInputProps, 'onChange'>['onChange']
10
+ value?: RichDate
11
+ }
12
+
13
+ export const TimezoneSelector = (props: TimezoneSelectorProps) => {
14
+ const {onChange, value} = props
15
+ const currentTz = allTimezones.find((tz) => tz.name === value?.timezone)
16
+ const userTz = allTimezones.find(
17
+ (tz) => tz.name === Intl.DateTimeFormat().resolvedOptions().timeZone,
18
+ )!
19
+
20
+ const handleTimezoneChange = (selectedTz: string) => {
21
+ const newTimezone =
22
+ allTimezones.find((tz) => tz.value === selectedTz) ?? (userTz as NormalizedTimeZone)
23
+
24
+ const offset = newTimezone.currentTimeOffsetInMinutes ?? 0
25
+ const timezonePatch = set(newTimezone.name, ['timezone'])
26
+ const offsetPatch = set(offset, ['offset'])
27
+ const patches = [timezonePatch, offsetPatch]
28
+
29
+ //then, recalculate UTC and local from "old" time with the new offset
30
+ if (value?.utc) {
31
+ const desiredDateTime = unlocalizeDateTime(value.utc, value.timezone)
32
+ const newUtcDate = zonedTimeToUtc(desiredDateTime, newTimezone.name).toISOString()
33
+ const newLocalDate = formatInTimeZone(
34
+ newUtcDate,
35
+ newTimezone.name,
36
+ "yyyy-MM-dd'T'HH:mm:ssXXX",
37
+ )
38
+ patches.push(set(newUtcDate, ['utc']))
39
+ patches.push(set(newLocalDate, ['local']))
40
+ }
41
+ onChange(patches)
42
+ }
43
+
44
+ return (
45
+ //taken from Scheduled Publishing, again!
46
+ //https://github.com/sanity-io/sanity-plugin-scheduled-publishing/blob/bb282e3df9a8a73df37fab8ee1fdd0e2430745be/src/components/dialogs/DialogTimeZone.tsx#L100
47
+ <Box padding={4}>
48
+ <Autocomplete
49
+ fontSize={2}
50
+ icon={SearchIcon}
51
+ id="timezone"
52
+ onChange={handleTimezoneChange}
53
+ openButton
54
+ options={allTimezones}
55
+ padding={4}
56
+ placeholder="Search for a city or time zone"
57
+ popover={{
58
+ boundaryElement: document.querySelector('body'),
59
+ constrainSize: true,
60
+ placement: 'bottom-start',
61
+ }}
62
+ renderOption={(option) => {
63
+ return (
64
+ <Card as="button" padding={3}>
65
+ <Text size={1} textOverflow="ellipsis">
66
+ <span>GMT{option.offset}</span>
67
+ <span style={{fontWeight: 500, marginLeft: '1em'}}>{option.alternativeName}</span>
68
+ <span style={{marginLeft: '1em'}}>{option.mainCities}</span>
69
+ </Text>
70
+ </Card>
71
+ )
72
+ }}
73
+ renderValue={(_, option) => {
74
+ if (!option) return ''
75
+ return `${option.alternativeName} (${option.namePretty})`
76
+ }}
77
+ tabIndex={-1}
78
+ value={currentTz?.value ?? userTz.value}
79
+ />
80
+ </Box>
81
+ )
82
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ import {definePlugin} from 'sanity'
2
+ import {richDateSchema, RichDateDefinition, RichDateSchemaType} from './schema'
3
+ import {RichDate} from './types'
4
+
5
+ export const richDate = definePlugin({
6
+ name: 'v3-rich-date-input',
7
+ schema: {
8
+ types: [richDateSchema],
9
+ },
10
+ })
11
+
12
+ export type {RichDateDefinition, RichDateSchemaType, RichDate}
package/src/schema.ts ADDED
@@ -0,0 +1,61 @@
1
+ import {
2
+ DatetimeDefinition,
3
+ ObjectDefinition,
4
+ ObjectSchemaType,
5
+ defineField,
6
+ defineType,
7
+ } from 'sanity'
8
+ import {RichDateInput} from './components/RichDateInput'
9
+
10
+ const richDateTypeName = 'richDate' as const
11
+
12
+ export type RichDateSchemaType = Omit<ObjectSchemaType, 'options'> & {
13
+ options?: DatetimeDefinition['options']
14
+ }
15
+
16
+ /**
17
+ * @public
18
+ */
19
+ export interface RichDateDefinition extends Omit<ObjectDefinition, 'type' | 'fields' | 'options'> {
20
+ type: typeof richDateTypeName
21
+ options?: DatetimeDefinition['options']
22
+ }
23
+
24
+ declare module 'sanity' {
25
+ //allows the custom input to be valid for the schema def
26
+ export interface IntrinsicDefinitions {
27
+ richDate: RichDateDefinition
28
+ }
29
+ }
30
+
31
+ export const richDateSchema = defineType({
32
+ name: richDateTypeName,
33
+ title: 'Rich Date',
34
+ type: 'object',
35
+ fields: [
36
+ defineField({
37
+ name: 'local',
38
+ title: 'Local',
39
+ type: 'string',
40
+ }),
41
+ defineField({
42
+ name: 'utc',
43
+ title: 'UTC',
44
+ type: 'string',
45
+ }),
46
+ defineField({
47
+ name: 'timezone',
48
+ title: 'Timezone',
49
+ type: 'string',
50
+ }),
51
+ defineField({
52
+ name: 'offset',
53
+ title: 'Offset',
54
+ type: 'number',
55
+ }),
56
+ ],
57
+
58
+ components: {
59
+ input: RichDateInput,
60
+ },
61
+ })
@@ -0,0 +1,17 @@
1
+ export interface RichDate {
2
+ local: string
3
+ utc: string
4
+ timezone: string
5
+ offset: number
6
+ }
7
+
8
+ export interface NormalizedTimeZone {
9
+ abbreviation: string
10
+ alternativeName: string
11
+ mainCities: string
12
+ name: string
13
+ namePretty: string
14
+ offset: string
15
+ value: string
16
+ currentTimeOffsetInMinutes: number
17
+ }
@@ -0,0 +1,39 @@
1
+ import {getTimeZones} from '@vvo/tzdb'
2
+ import {NormalizedTimeZone} from '../types'
3
+ import {formatInTimeZone} from 'date-fns-tz'
4
+
5
+ export const unlocalizeDateTime = (datetime: string, timezone: string): string => {
6
+ return formatInTimeZone(datetime, timezone, 'yyyy-MM-dd HH:mm:ss')
7
+ }
8
+
9
+ /* We have to "fake" a UTC date to make the datepicker look "right"
10
+ * to the user. For example, if someone sets 7:00AM PST, which is 3PM UTC
11
+ * and I am on the east coast, I want to have 12:00PM UTC, which will look like 7:00AM to me
12
+ * In other words, UTC minus 3 hours, or (UTC(my offset - their offset))
13
+ * this is purely cosmetic and should not be saved at all
14
+ */
15
+ export const getConstructedUTCDate = (utc: string, offset: number): string => {
16
+ const date = new Date(utc)
17
+ const currentOffset = new Date().getTimezoneOffset() * -1
18
+ const diff = currentOffset - offset
19
+ const fakeUTCDate = new Date(date.getTime() - diff * 60 * 1000)
20
+ return fakeUTCDate.toISOString()
21
+ }
22
+
23
+ //keep some consistency with scheduled publishing
24
+ //https://github.com/sanity-io/sanity-plugin-scheduled-publishing/blob/bb282e3df9a8a73df37fab8ee1fdd0e2430745be/src/hooks/useTimeZone.tsx#L17
25
+ export const allTimezones = getTimeZones().map((tz) => {
26
+ return {
27
+ abbreviation: tz.abbreviation,
28
+ alternativeName: tz.alternativeName,
29
+ mainCities: tz.mainCities.join(', '),
30
+ // Main time zone name 'Africa/Dar_es_Salaam'
31
+ name: tz.name,
32
+ // Time zone name with underscores removed
33
+ namePretty: tz.name.replaceAll('_', ' '),
34
+ offset: tz.currentTimeFormat.split(' ')[0],
35
+ // all searchable text - this is transformed before being rendered in `<AutoComplete>`
36
+ value: `${tz.currentTimeFormat} ${tz.abbreviation} ${tz.name}`,
37
+ currentTimeOffsetInMinutes: tz.currentTimeOffsetInMinutes,
38
+ } as NormalizedTimeZone
39
+ })
@@ -0,0 +1,11 @@
1
+ const {showIncompatiblePluginDialog} = require('@sanity/incompatible-plugin')
2
+ const {name, version, sanityExchangeUrl} = require('./package.json')
3
+
4
+ export default showIncompatiblePluginDialog({
5
+ name: name,
6
+ versions: {
7
+ v3: version,
8
+ v2: undefined,
9
+ },
10
+ sanityExchangeUrl,
11
+ })
package/lib/RichDate.css DELETED
@@ -1,28 +0,0 @@
1
- @import 'part:@sanity/base/theme/variables-style';
2
-
3
- .input {
4
- composes: textInput from 'part:@sanity/base/theme/forms/text-input-style';
5
- width: 100%;
6
- }
7
-
8
- .datepicker {
9
- composes: shadow-6dp from 'part:@sanity/base/theme/shadows-style';
10
- position: relative;
11
- }
12
-
13
- .root {
14
- position: relative;
15
- }
16
-
17
- .rootWithTime {
18
- composes: root;
19
- }
20
-
21
- .deprecationWarning {
22
- padding: var(--medium-padding);
23
- border: 3px dashed var(--state-warning-color);
24
-
25
- @nest & > code {
26
- font-weight: bold;
27
- }
28
- }
package/lib/RichDate.js DELETED
@@ -1,163 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _momentTimezone = _interopRequireDefault(require("moment-timezone"));
9
-
10
- var _generateHelpUrl = _interopRequireDefault(require("@sanity/generate-help-url"));
11
-
12
- var _propTypes = _interopRequireDefault(require("prop-types"));
13
-
14
- var _react = _interopRequireDefault(require("react"));
15
-
16
- var _default = _interopRequireDefault(require("part:@sanity/components/formfields/default"));
17
-
18
- var _RichDate = _interopRequireDefault(require("./RichDate.css"));
19
-
20
- var _patchEvent = require("part:@sanity/form-builder/patch-event");
21
-
22
- var _reactDatepicker = _interopRequireDefault(require("react-datepicker"));
23
-
24
- require("react-datepicker/dist/react-datepicker-cssmodules.css");
25
-
26
- var _default2 = _interopRequireDefault(require("part:@sanity/components/selects/default"));
27
-
28
- var _util = require("./util");
29
-
30
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
31
-
32
- function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
33
-
34
- function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
35
-
36
- var DEPRECATION_WARNING = /*#__PURE__*/_react.default.createElement("div", {
37
- className: _RichDate.default.deprecationWarning
38
- }, "This field has ", /*#__PURE__*/_react.default.createElement("code", null, "type: ", 'date'), ", which is deprecated and should be changed to", ' ', /*#__PURE__*/_react.default.createElement("code", null, "type: ", 'richDate'), ". Please update your schema and migrate your data.", ' ', /*#__PURE__*/_react.default.createElement("a", {
39
- href: (0, _generateHelpUrl.default)('migrate-to-rich-date'),
40
- target: "_blank",
41
- rel: "noopener noreferrer"
42
- }, "More info"));
43
-
44
- class RichDateInput extends _react.default.PureComponent {
45
- constructor() {
46
- super(...arguments);
47
-
48
- _defineProperty(this, "handleChange", nextValue => {
49
- var onChange = this.props.onChange;
50
- var assembledValue = this.assembleOutgoingValue(nextValue);
51
- onChange(_patchEvent.PatchEvent.from(assembledValue ? (0, _patchEvent.set)(assembledValue) : (0, _patchEvent.unset)()));
52
- });
53
-
54
- _defineProperty(this, "handleTimeChange", nextValue => {
55
- var onChange = this.props.onChange;
56
- var assembledValue = this.assembleOutgoingValue(nextValue.value);
57
- onChange(_patchEvent.PatchEvent.from(assembledValue ? (0, _patchEvent.set)(assembledValue) : (0, _patchEvent.unset)()));
58
- });
59
-
60
- _defineProperty(this, "getCurrentValue", () => {
61
- var value = this.props.value;
62
-
63
- if (!value) {
64
- return null;
65
- }
66
-
67
- return (0, _util.getOptions)(this.props).inputUtc ? value.utc : value.local;
68
- });
69
- }
70
-
71
- assembleOutgoingValue(newMoment) {
72
- if (!newMoment || !newMoment.isValid()) {
73
- return undefined;
74
- }
75
-
76
- var name = this.props.type.name;
77
-
78
- if ((0, _util.getOptions)(this.props).inputUtc) {
79
- return {
80
- _type: name,
81
- utc: newMoment.utc().format() // e.g. "2017-02-12T09:15:00Z"
82
-
83
- };
84
- }
85
-
86
- return {
87
- _type: name,
88
- local: newMoment.format(),
89
- // e.g. 2017-02-21T10:15:00+01:00
90
- utc: newMoment.utc().format(),
91
- // e.g. 2017-02-12T09:15:00Z
92
- timezone: _momentTimezone.default.tz.guess(),
93
- // e.g. Europe/Oslo
94
- offset: (0, _momentTimezone.default)().utcOffset() // e.g. 60 (utc offset in minutes)
95
-
96
- };
97
- }
98
-
99
- render() {
100
- var _this$props = this.props,
101
- value = _this$props.value,
102
- type = _this$props.type,
103
- level = _this$props.level;
104
- var title = type.title,
105
- description = type.description;
106
- var options = (0, _util.getOptions)(this.props);
107
- var format = [options.inputDate ? options.dateFormat : null, options.inputTime ? options.timeFormat : null].filter(Boolean).join(' ');
108
- var timeIntervals = (0, _util.getTimeIntervals)(value, options);
109
- var activeTimeInterval = timeIntervals.find(time => time.isActive === true);
110
- var placeholder = typeof type.placeholder === 'function' ? type.placeholder() : type.placeholder;
111
- return /*#__PURE__*/_react.default.createElement(_default.default, {
112
- labelFor: this.inputId,
113
- label: title,
114
- level: level,
115
- description: description
116
- }, type.name === 'date' && DEPRECATION_WARNING, /*#__PURE__*/_react.default.createElement("div", {
117
- className: options.inputTime ? _RichDate.default.rootWithTime : _RichDate.default.root
118
- }, options.inputDate && /*#__PURE__*/_react.default.createElement(_reactDatepicker.default, _extends({}, options, {
119
- showMonthDropdown: true,
120
- showYearDropdown: true,
121
- todayButton: options.calendarTodayLabel,
122
- selected: value && (0, _momentTimezone.default)(options.inputUtc ? value.utc : value.local),
123
- placeholderText: placeholder,
124
- calendarClassName: _RichDate.default.datepicker,
125
- className: _RichDate.default.input,
126
- onChange: this.handleChange,
127
- value: value && (0, _momentTimezone.default)(options.inputUtc ? value.utc : value.local).format(format),
128
- showTimeSelect: options.inputTime,
129
- dateFormat: options.dateFormat,
130
- timeFormat: options.timeFormat,
131
- timeIntervals: options.timeStep
132
- })), !options.inputDate && options.inputTime && /*#__PURE__*/_react.default.createElement(_default2.default, {
133
- items: timeIntervals,
134
- value: activeTimeInterval,
135
- onChange: this.handleTimeChange
136
- })));
137
- }
138
-
139
- }
140
-
141
- exports.default = RichDateInput;
142
- RichDateInput.propTypes = {
143
- value: _propTypes.default.shape({
144
- utc: _propTypes.default.string,
145
- local: _propTypes.default.string,
146
- timezone: _propTypes.default.string,
147
- offset: _propTypes.default.number
148
- }),
149
- type: _propTypes.default.shape({
150
- title: _propTypes.default.string.isRequired,
151
- name: _propTypes.default.string.isRequired,
152
- options: _propTypes.default.object
153
- }),
154
- onChange: _propTypes.default.func,
155
- level: _propTypes.default.number
156
- };
157
- RichDateInput.contextTypes = {
158
- resolveInputComponent: _propTypes.default.func,
159
- schema: _propTypes.default.object,
160
- intl: _propTypes.default.shape({
161
- locale: _propTypes.default.string
162
- })
163
- };
package/lib/index.js DELETED
@@ -1,23 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- Object.defineProperty(exports, "default", {
7
- enumerable: true,
8
- get: function get() {
9
- return _RichDate.default;
10
- }
11
- });
12
- Object.defineProperty(exports, "schema", {
13
- enumerable: true,
14
- get: function get() {
15
- return _schema.default;
16
- }
17
- });
18
-
19
- var _RichDate = _interopRequireDefault(require("./RichDate"));
20
-
21
- var _schema = _interopRequireDefault(require("./schema"));
22
-
23
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
package/lib/schema.js DELETED
@@ -1,36 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _RichDate = _interopRequireDefault(require("./RichDate"));
9
-
10
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
-
12
- var _default = {
13
- title: 'Rich Date',
14
- name: 'richDate',
15
- type: 'object',
16
- fields: [{
17
- name: 'utc',
18
- type: 'datetime',
19
- title: 'UTC',
20
- required: true
21
- }, {
22
- name: 'local',
23
- type: 'datetime',
24
- title: 'Local'
25
- }, {
26
- name: 'timezone',
27
- type: 'string',
28
- title: 'Timezone'
29
- }, {
30
- name: 'offset',
31
- type: 'number',
32
- title: 'Offset'
33
- }],
34
- inputComponent: _RichDate.default
35
- };
36
- exports.default = _default;
package/lib/story.js DELETED
@@ -1,42 +0,0 @@
1
- "use strict";
2
-
3
- var _react = _interopRequireDefault(require("react"));
4
-
5
- var _storybook = require("part:@sanity/storybook");
6
-
7
- var _richDate = _interopRequireDefault(require("part:@sanity/form-builder/input/rich-date"));
8
-
9
- var _knobs = require("part:@sanity/storybook/addons/knobs");
10
-
11
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
-
13
- (0, _storybook.storiesOf)('Date Picker', module).addDecorator(_knobs.withKnobs).add('Default', () => {
14
- var dateFormat = (0, _knobs.text)('dateFormat', 'YYYY-MM-DD');
15
- var timeFormat = (0, _knobs.text)('timeFormat', 'HH:mm');
16
- var inputUtc = (0, _knobs.boolean)('inputUtc', false);
17
- var timeStep = (0, _knobs.number)('timeStep', 15);
18
- var calendarTodayLabel = (0, _knobs.text)('calendarTodayLabel', 'Today');
19
- var placeholderDate = (0, _knobs.text)('placeholderDate');
20
- var placeholderTime = (0, _knobs.text)('placeholderTime');
21
- var inputDate = (0, _knobs.boolean)('inputDate', true);
22
- var inputTime = (0, _knobs.boolean)('inputTime', true);
23
- var options = {
24
- dateFormat,
25
- timeFormat,
26
- inputUtc,
27
- timeStep,
28
- calendarTodayLabel,
29
- placeholderDate,
30
- placeholderTime,
31
- inputDate,
32
- inputTime
33
- };
34
- return /*#__PURE__*/_react.default.createElement(_richDate.default, {
35
- onChange: (0, _storybook.action)('onChange'),
36
- type: {
37
- title: (0, _knobs.text)('title'),
38
- description: (0, _knobs.text)('description'),
39
- options: options
40
- }
41
- });
42
- });
package/lib/util.js DELETED
@@ -1,52 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.getOptions = getOptions;
7
- exports.getPlaceholderText = getPlaceholderText;
8
- exports.getTimeIntervals = getTimeIntervals;
9
-
10
- var _moment = _interopRequireDefault(require("moment"));
11
-
12
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
-
14
- function getOptions(props) {
15
- var options = Object.assign({}, props.type.options);
16
- options.dateFormat = options.dateFormat || 'YYYY-MM-DD';
17
- options.timeFormat = options.timeFormat || 'HH:mm';
18
- options.inputUtc = options.inputUtc === true;
19
- options.timeStep = options.timeStep || 15;
20
- options.calendarTodayLabel = options.calendarTodayLabel || 'Today';
21
- options.inputDate = options.hasOwnProperty('inputDate') ? options.inputDate : true;
22
- options.inputTime = options.hasOwnProperty('inputTime') ? options.inputTime : true;
23
- options.placeholderDate = options.placeholderDate || (0, _moment.default)().format(options.dateFormat);
24
- options.placeholderTime = options.placeholderTime || (0, _moment.default)().format(options.timeFormat);
25
- return options;
26
- }
27
-
28
- function getPlaceholderText(options) {
29
- return "".concat(options.inputDate ? options.placeholderDate : '', " ").concat(options.inputTime ? options.placeholderTime : '');
30
- }
31
-
32
- function getTimeIntervals(value, options) {
33
- var timeStep = options.timeStep,
34
- timeFormat = options.timeFormat;
35
- var activeTime = value && (0, _moment.default)(value);
36
- var beginningOfDay = (0, _moment.default)().startOf('day');
37
- var multiplier = 1440 / timeStep;
38
- var timeIntervals = [];
39
-
40
- for (var i = 0; i < multiplier; i++) {
41
- timeIntervals.push(beginningOfDay.clone().add(i * timeStep, 'minutes'));
42
- }
43
-
44
- return timeIntervals.map(time => {
45
- var isActive = activeTime && time.format(options.timeFormat) === activeTime.format(options.timeFormat);
46
- return {
47
- title: time.format(timeFormat),
48
- value: time,
49
- isActive: isActive
50
- };
51
- });
52
- }
package/schema.js DELETED
@@ -1 +0,0 @@
1
- module.exports = require('./lib/schema')