@sanity/rich-date-input 3.0.7 → 4.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.
@@ -1,68 +0,0 @@
1
- import {formatInTimeZone, getTimezoneOffset, zonedTimeToUtc} from 'date-fns-tz'
2
- import {type ReactNode, useCallback} from 'react'
3
- import {DateTimeInput, FieldProps, FormPatch, PatchEvent, set, unset} from 'sanity'
4
-
5
- import {RichDate} from '../types'
6
- import {getConstructedUTCDate, unlocalizeDateTime} from '../utils'
7
-
8
- interface RelativeDateTimePickerProps extends Omit<FieldProps, 'renderDefault'> {
9
- dateValue?: RichDate
10
- }
11
- export const RelativeDateTimePicker = (props: RelativeDateTimePickerProps): ReactNode => {
12
- const {
13
- dateValue: value,
14
- inputProps: {onChange},
15
- } = props
16
-
17
- const handleDateChange = useCallback(
18
- (patch: FormPatch | PatchEvent | FormPatch[]) => {
19
- const timezone = value?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone
20
- const newDatetime = (patch as unknown as {value: string})?.value
21
- if (!newDatetime || !('type' in patch) || patch.type !== 'set') {
22
- onChange(unset())
23
- return
24
- }
25
-
26
- /* get what time the user "meant" to set without tz info
27
- * right now, newDatetime is the time the user set plus
28
- * their current offset, not the timezone offset
29
- */
30
- const desiredDateTime = unlocalizeDateTime(
31
- newDatetime,
32
- Intl.DateTimeFormat().resolvedOptions().timeZone,
33
- )
34
-
35
- const newUtcDateObject = zonedTimeToUtc(desiredDateTime, timezone)
36
- // offset may have changed based on DST, capture that
37
- const newOffset = getTimezoneOffset(timezone, newUtcDateObject) / 60 / 1000
38
- const localDate = formatInTimeZone(newUtcDateObject, timezone, "yyyy-MM-dd'T'HH:mm:ssXXX")
39
-
40
- const patches = []
41
-
42
- patches.push(set(newUtcDateObject.toISOString(), ['utc']))
43
- patches.push(set(localDate, ['local']))
44
-
45
- if (!value?.timezone) {
46
- patches.push(set(timezone, ['timezone']))
47
- }
48
-
49
- if (value?.offset !== newOffset) {
50
- patches.push(set(newOffset, ['offset']))
51
- }
52
-
53
- onChange(patches)
54
- },
55
- [onChange, value],
56
- )
57
-
58
- // Dynamically calculate the offset for the actual event date to handle DST correctly
59
- const displayOffset =
60
- value?.utc && value?.timezone
61
- ? getTimezoneOffset(value.timezone, new Date(value.utc)) / 60 / 1000
62
- : (value?.offset ?? 0)
63
-
64
- const dateToDisplay = value?.utc ? getConstructedUTCDate(value.utc, displayOffset) : ''
65
-
66
- // @ts-expect-error -- slight mismatch in elementProps and renderDefault, but should line up in practice
67
- return <DateTimeInput {...props} onChange={handleDateChange} value={dateToDisplay} />
68
- }
@@ -1,61 +0,0 @@
1
- import {Box, Dialog, Flex} from '@sanity/ui'
2
- import {type ReactNode, useCallback, useState} from 'react'
3
- import {ObjectInputMember, ObjectInputProps} from 'sanity'
4
-
5
- import {RichDate} from '../types'
6
- import {RelativeDateTimePicker} from './RelativeDateTimePicker'
7
- import {TimezoneButton} from './TimezoneButton'
8
- import {TimezoneSelector} from './TimezoneSelector'
9
-
10
- export const RichDateInput = (props: ObjectInputProps): ReactNode => {
11
- const {onChange, value, members, schemaType} = props
12
- const {options} = schemaType
13
- const localMember = members.find((member) => member.kind === 'field' && member.name === 'local')
14
- const timezoneMember = members.find(
15
- (member) => member.kind === 'field' && member.name === 'timezone',
16
- )
17
- const [timezoneSelectorOpen, setTimezoneSelectorOpen] = useState(false)
18
- const onClose = useCallback(() => setTimezoneSelectorOpen(false), [])
19
- const onOpen = useCallback(() => setTimezoneSelectorOpen(true), [])
20
-
21
- return (
22
- <>
23
- <Flex>
24
- <Box flex={[1, 2, 4]}>
25
- {localMember && (
26
- <ObjectInputMember
27
- {...props}
28
- member={localMember}
29
- // eslint-disable-next-line react/jsx-no-bind
30
- renderField={(renderFieldProps) => (
31
- <RelativeDateTimePicker
32
- {...renderFieldProps}
33
- dateValue={value as RichDate}
34
- schemaType={{...renderFieldProps.schemaType, options}}
35
- inputProps={{...renderFieldProps.inputProps, onChange: onChange}}
36
- />
37
- )}
38
- />
39
- )}
40
- </Box>
41
- <Box flex={[1]} marginLeft={[2, 2, 3, 4]} marginTop={2}>
42
- {timezoneMember && (
43
- <ObjectInputMember
44
- {...props}
45
- member={timezoneMember}
46
- // eslint-disable-next-line react/jsx-no-bind
47
- renderInput={() => (
48
- <TimezoneButton onClick={onOpen} timezone={value?.timezone ?? ''} />
49
- )}
50
- />
51
- )}
52
- </Box>
53
- </Flex>
54
- {timezoneSelectorOpen && (
55
- <Dialog onClose={onClose} header="Select a timezone" id="timezone-select" width={1}>
56
- <TimezoneSelector onChange={onChange} value={value as RichDate} />
57
- </Dialog>
58
- )}
59
- </>
60
- )
61
- }
@@ -1,34 +0,0 @@
1
- import {EarthAmericasIcon} from '@sanity/icons'
2
- import {Button} from '@sanity/ui'
3
- import {type ReactNode} from 'react'
4
-
5
- import {allTimezones} from '../utils'
6
-
7
- interface TimezoneButtonProps {
8
- onClick: () => void
9
- timezone: string
10
- }
11
-
12
- export const TimezoneButton = (props: TimezoneButtonProps): ReactNode => {
13
- const {onClick, timezone} = props
14
- const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone
15
-
16
- const label =
17
- allTimezones.find((tz) => tz.name === timezone)?.abbreviation ??
18
- allTimezones.find((tz) => tz.group.includes(timezone))?.abbreviation ??
19
- allTimezones.find((tz) => tz.name === currentTimezone)?.abbreviation ??
20
- allTimezones.find((tz) => tz.group.includes(currentTimezone))?.abbreviation
21
-
22
- return (
23
- <Button
24
- fontSize={1}
25
- style={{width: '100%'}}
26
- justify={'flex-start'}
27
- icon={EarthAmericasIcon}
28
- mode="ghost"
29
- onClick={onClick}
30
- text={`${label}`}
31
- aria-label="Select a timezone"
32
- />
33
- )
34
- }
@@ -1,89 +0,0 @@
1
- import {SearchIcon} from '@sanity/icons'
2
- import {Autocomplete, Box, Card, Text} from '@sanity/ui'
3
- import {formatInTimeZone, getTimezoneOffset, zonedTimeToUtc} from 'date-fns-tz'
4
- import {type ReactNode, useCallback} from 'react'
5
- import {ObjectInputProps, set} from 'sanity'
6
-
7
- import {NormalizedTimeZone, RichDate} from '../types'
8
- import {allTimezones, unlocalizeDateTime} from '../utils'
9
-
10
- interface TimezoneSelectorProps {
11
- onChange: Pick<ObjectInputProps, 'onChange'>['onChange']
12
- value?: RichDate
13
- }
14
-
15
- export const TimezoneSelector = (props: TimezoneSelectorProps): ReactNode => {
16
- const {onChange, value} = props
17
- const currentTz = allTimezones.find((tz) => tz.name === value?.timezone)
18
- const userTzName = Intl.DateTimeFormat().resolvedOptions().timeZone
19
- const userTz = (allTimezones.find((tz) => tz.name === userTzName) ??
20
- allTimezones.find((tz) => tz.group.includes(userTzName)))!
21
-
22
- const handleTimezoneChange = useCallback(
23
- (selectedTz: string) => {
24
- const newTimezone =
25
- allTimezones.find((tz) => tz.value === selectedTz) ?? (userTz as NormalizedTimeZone)
26
-
27
- const timezonePatch = set(newTimezone.name, ['timezone'])
28
- const patches = [timezonePatch]
29
-
30
- // then, recalculate UTC and local from "old" time with the new offset
31
- if (value?.utc) {
32
- const desiredDateTime = unlocalizeDateTime(value.utc, value.timezone)
33
- const newUtcDateObject = zonedTimeToUtc(desiredDateTime, newTimezone.name)
34
- const newOffset = getTimezoneOffset(newTimezone.name, newUtcDateObject) / 60 / 1000
35
- const newLocalDate = formatInTimeZone(
36
- newUtcDateObject.toISOString(),
37
- newTimezone.name,
38
- "yyyy-MM-dd'T'HH:mm:ssXXX",
39
- )
40
- patches.push(set(newUtcDateObject.toISOString(), ['utc']))
41
- patches.push(set(newLocalDate, ['local']))
42
- patches.push(set(newOffset, ['offset']))
43
- }
44
- onChange(patches)
45
- },
46
- [onChange, userTz, value],
47
- )
48
-
49
- return (
50
- // taken from Scheduled Publishing, again!
51
- // https://github.com/sanity-io/sanity-plugin-scheduled-publishing/blob/bb282e3df9a8a73df37fab8ee1fdd0e2430745be/src/components/dialogs/DialogTimeZone.tsx#L100
52
- <Box padding={4}>
53
- <Autocomplete
54
- fontSize={2}
55
- icon={SearchIcon}
56
- id="timezone"
57
- onChange={handleTimezoneChange}
58
- openButton
59
- options={allTimezones}
60
- padding={4}
61
- placeholder="Search for a city or time zone"
62
- popover={{
63
- boundaryElement: document.querySelector('body'),
64
- constrainSize: true,
65
- placement: 'bottom-start',
66
- }}
67
- // eslint-disable-next-line react/jsx-no-bind
68
- renderOption={(option) => {
69
- return (
70
- <Card as="button" padding={3}>
71
- <Text size={1} textOverflow="ellipsis">
72
- <span>GMT{option.offset}</span>
73
- <span style={{fontWeight: 500, marginLeft: '1em'}}>{option.alternativeName}</span>
74
- <span style={{marginLeft: '1em'}}>{option.mainCities}</span>
75
- </Text>
76
- </Card>
77
- )
78
- }}
79
- // eslint-disable-next-line react/jsx-no-bind
80
- renderValue={(_, option) => {
81
- if (!option) return ''
82
- return `${option.alternativeName} (${option.namePretty})`
83
- }}
84
- tabIndex={-1}
85
- value={currentTz?.value ?? userTz.value}
86
- />
87
- </Box>
88
- )
89
- }
package/src/index.ts DELETED
@@ -1,13 +0,0 @@
1
- import {definePlugin} from 'sanity'
2
-
3
- import {RichDateDefinition, richDateSchema, RichDateSchemaType} from './schema'
4
- import {RichDate} from './types'
5
-
6
- export const richDate = definePlugin({
7
- name: 'v3-rich-date-input',
8
- schema: {
9
- types: [richDateSchema],
10
- },
11
- })
12
-
13
- export type {RichDate, RichDateDefinition, RichDateSchemaType}
package/src/schema.ts DELETED
@@ -1,62 +0,0 @@
1
- import {
2
- DatetimeDefinition,
3
- defineField,
4
- defineType,
5
- ObjectDefinition,
6
- ObjectSchemaType,
7
- } from 'sanity'
8
-
9
- import {RichDateInput} from './components/RichDateInput'
10
-
11
- const richDateTypeName = 'richDate' as const
12
-
13
- export type RichDateSchemaType = Omit<ObjectSchemaType, 'options'> & {
14
- options?: DatetimeDefinition['options']
15
- }
16
-
17
- /**
18
- * @public
19
- */
20
- export interface RichDateDefinition extends Omit<ObjectDefinition, 'type' | 'fields' | 'options'> {
21
- type: typeof richDateTypeName
22
- options?: DatetimeDefinition['options']
23
- }
24
-
25
- declare module 'sanity' {
26
- //allows the custom input to be valid for the schema def
27
- export interface IntrinsicDefinitions {
28
- richDate: RichDateDefinition
29
- }
30
- }
31
-
32
- export const richDateSchema = defineType({
33
- name: richDateTypeName,
34
- title: 'Rich Date',
35
- type: 'object',
36
- fields: [
37
- defineField({
38
- name: 'local',
39
- title: 'Local',
40
- type: 'string',
41
- }),
42
- defineField({
43
- name: 'utc',
44
- title: 'UTC',
45
- type: 'string',
46
- }),
47
- defineField({
48
- name: 'timezone',
49
- title: 'Timezone',
50
- type: 'string',
51
- }),
52
- defineField({
53
- name: 'offset',
54
- title: 'Offset',
55
- type: 'number',
56
- }),
57
- ],
58
-
59
- components: {
60
- input: RichDateInput,
61
- },
62
- })
@@ -1,18 +0,0 @@
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
- group: string[]
18
- }
@@ -1,41 +0,0 @@
1
- import {getTimeZones} from '@vvo/tzdb'
2
- import {formatInTimeZone} from 'date-fns-tz'
3
-
4
- import {NormalizedTimeZone} from '../types'
5
-
6
- export const unlocalizeDateTime = (datetime: string, timezone: string): string => {
7
- return formatInTimeZone(datetime, timezone, 'yyyy-MM-dd HH:mm:ss')
8
- }
9
-
10
- /* We have to "fake" a UTC date to make the datepicker look "right"
11
- * to the user. For example, if someone sets 7:00AM PST, which is 3PM UTC
12
- * and I am on the east coast, I want to have 12:00PM UTC, which will look like 7:00AM to me
13
- * In other words, UTC minus 3 hours, or (UTC(my offset - their offset))
14
- * this is purely cosmetic and should not be saved at all
15
- */
16
- export const getConstructedUTCDate = (utc: string, offset: number): string => {
17
- const date = new Date(utc)
18
- const currentOffset = date.getTimezoneOffset() * -1
19
- const diff = currentOffset - offset
20
- const fakeUTCDate = new Date(date.getTime() - diff * 60 * 1000)
21
- return fakeUTCDate.toISOString()
22
- }
23
-
24
- //keep some consistency with scheduled publishing
25
- //https://github.com/sanity-io/sanity-plugin-scheduled-publishing/blob/bb282e3df9a8a73df37fab8ee1fdd0e2430745be/src/hooks/useTimeZone.tsx#L17
26
- export const allTimezones = getTimeZones().map((tz) => {
27
- return {
28
- abbreviation: tz.abbreviation,
29
- alternativeName: tz.alternativeName,
30
- mainCities: tz.mainCities.join(', '),
31
- // Main time zone name 'Africa/Dar_es_Salaam'
32
- name: tz.name,
33
- // Time zone name with underscores removed
34
- namePretty: tz.name.replaceAll('_', ' '),
35
- offset: tz.currentTimeFormat.split(' ')[0],
36
- // all searchable text - this is transformed before being rendered in `<AutoComplete>`
37
- value: `${tz.currentTimeFormat} ${tz.abbreviation} ${tz.name}`,
38
- currentTimeOffsetInMinutes: tz.currentTimeOffsetInMinutes,
39
- group: tz.group,
40
- } as NormalizedTimeZone
41
- })
@@ -1,11 +0,0 @@
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
- })