sanity-plugin-recurring-dates 1.3.1 → 1.4.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.
- package/README.md +22 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.esm.js +59 -24
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +58 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/CustomRule/CustomRule.tsx +31 -7
- package/src/components/RecurringDate.tsx +59 -24
- package/src/components/RecurringDatesPreview.tsx +14 -1
- package/src/schema/recurringDates.tsx +5 -3
- package/src/types.ts +8 -0
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import {format, toDate} from 'date-fns-tz'
|
|
|
3
3
|
import React, {useCallback, useMemo, useState} from 'react'
|
|
4
4
|
import {Options, RRule, rrulestr, Weekday} from 'rrule'
|
|
5
5
|
import {type ObjectInputProps, set} from 'sanity'
|
|
6
|
+
import {Feedback} from 'sanity-plugin-utils'
|
|
6
7
|
|
|
7
8
|
import {DEFAULT_COUNTS} from '../../constants'
|
|
8
9
|
import {PluginConfig} from '../../types'
|
|
@@ -16,13 +17,15 @@ export function CustomRule({
|
|
|
16
17
|
onChange,
|
|
17
18
|
initialValue,
|
|
18
19
|
startDate,
|
|
20
|
+
endDate,
|
|
19
21
|
dateTimeOptions,
|
|
20
22
|
}: {
|
|
21
23
|
open: boolean
|
|
22
24
|
onClose: () => void
|
|
23
25
|
onChange: ObjectInputProps['onChange']
|
|
24
26
|
initialValue: string
|
|
25
|
-
startDate
|
|
27
|
+
startDate?: string
|
|
28
|
+
endDate?: string
|
|
26
29
|
dateTimeOptions: PluginConfig['dateTimeOptions']
|
|
27
30
|
}): React.JSX.Element {
|
|
28
31
|
const initialRule = useMemo(() => {
|
|
@@ -43,6 +46,8 @@ export function CustomRule({
|
|
|
43
46
|
initialRule.origOptions.byweekday || null,
|
|
44
47
|
)
|
|
45
48
|
|
|
49
|
+
const [untilValid, setUntilValid] = useState<boolean>(true)
|
|
50
|
+
|
|
46
51
|
const handleChange = useCallback(
|
|
47
52
|
(event: React.FormEvent<HTMLInputElement> | React.FormEvent<HTMLSelectElement>) => {
|
|
48
53
|
const {name, value} = event.currentTarget
|
|
@@ -76,11 +81,25 @@ export function CustomRule({
|
|
|
76
81
|
return fromDate
|
|
77
82
|
}, [frequency, startDate])
|
|
78
83
|
|
|
79
|
-
const handleUntilChange = useCallback(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
const handleUntilChange = useCallback(
|
|
85
|
+
(date: string | null) => {
|
|
86
|
+
if (date) {
|
|
87
|
+
const untilDate = toDate(`${date}T23:59:59`)
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
(endDate && untilDate < toDate(endDate)) ||
|
|
91
|
+
(startDate && untilDate < toDate(startDate))
|
|
92
|
+
) {
|
|
93
|
+
setUntilValid(false)
|
|
94
|
+
} else {
|
|
95
|
+
setUntilValid(true)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
setUntil(untilDate)
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
[endDate, startDate],
|
|
102
|
+
)
|
|
84
103
|
|
|
85
104
|
const handleEndChange = useCallback(
|
|
86
105
|
(event: React.FormEvent<HTMLInputElement>) => {
|
|
@@ -198,6 +217,11 @@ export function CustomRule({
|
|
|
198
217
|
readOnly={!until}
|
|
199
218
|
/>
|
|
200
219
|
</Box>
|
|
220
|
+
{!untilValid && (
|
|
221
|
+
<Feedback tone="critical">
|
|
222
|
+
<Text size={1}>Until date must be after event ends</Text>
|
|
223
|
+
</Feedback>
|
|
224
|
+
)}
|
|
201
225
|
</Flex>
|
|
202
226
|
<Flex gap={2} align="center">
|
|
203
227
|
<Radio
|
|
@@ -227,7 +251,7 @@ export function CustomRule({
|
|
|
227
251
|
<Box paddingX={4} paddingY={3} style={{borderTop: '1px solid var(--card-border-color)'}}>
|
|
228
252
|
<Flex gap={2} justify="flex-end">
|
|
229
253
|
<Button text="Cancel" mode="ghost" onClick={onClose} />
|
|
230
|
-
<Button text="Done" tone="positive" onClick={handleConfirm} />
|
|
254
|
+
<Button text="Done" tone="positive" onClick={handleConfirm} disabled={!untilValid} />
|
|
231
255
|
</Flex>
|
|
232
256
|
</Box>
|
|
233
257
|
</Flex>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {Box, Flex, Grid, Select, Stack, Text} from '@sanity/ui'
|
|
2
2
|
import {upperFirst} from 'lodash'
|
|
3
3
|
import React, {useCallback, useState} from 'react'
|
|
4
|
-
import {rrulestr} from 'rrule'
|
|
4
|
+
import {datetime, rrulestr} from 'rrule'
|
|
5
5
|
import {
|
|
6
6
|
ObjectInputMember,
|
|
7
7
|
type ObjectInputProps,
|
|
@@ -82,6 +82,31 @@ export function RecurringDates(props: RecurringDatesProps): React.JSX.Element {
|
|
|
82
82
|
...startDateMember?.field?.schemaType.options,
|
|
83
83
|
...dateTimeOptions,
|
|
84
84
|
}
|
|
85
|
+
|
|
86
|
+
// Ensure the right schemaType is set
|
|
87
|
+
if (dateOnly === true) {
|
|
88
|
+
startDateMember.field.schemaType.name = 'date'
|
|
89
|
+
} else {
|
|
90
|
+
startDateMember.field.schemaType.name = 'datetime'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Add custom validation to the start date field
|
|
94
|
+
if (validation?.startDate) {
|
|
95
|
+
startDateMember.field.schemaType.validation = (CustomValidation) =>
|
|
96
|
+
validation?.startDate?.(CustomValidation) as Rule
|
|
97
|
+
} else {
|
|
98
|
+
startDateMember.field.schemaType.validation = (DefaultRule) => DefaultRule.required() as Rule
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Add custom title to the start date field
|
|
102
|
+
if (options?.fieldTitles?.startDate) {
|
|
103
|
+
startDateMember.field.schemaType.title = options.fieldTitles.startDate
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Add custom description to the start date field
|
|
107
|
+
if (options?.fieldDescriptions?.startDate) {
|
|
108
|
+
startDateMember.field.schemaType.description = options.fieldDescriptions.startDate
|
|
109
|
+
}
|
|
85
110
|
}
|
|
86
111
|
|
|
87
112
|
if (endDateMember?.kind == 'field') {
|
|
@@ -89,36 +114,32 @@ export function RecurringDates(props: RecurringDatesProps): React.JSX.Element {
|
|
|
89
114
|
...endDateMember?.field?.schemaType.options,
|
|
90
115
|
...dateTimeOptions,
|
|
91
116
|
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (dateOnly === true) {
|
|
95
|
-
if (startDateMember?.kind == 'field') {
|
|
96
|
-
startDateMember.field.schemaType.name = 'date'
|
|
97
|
-
}
|
|
98
117
|
|
|
99
|
-
|
|
118
|
+
// Ensure the right schemaType is set
|
|
119
|
+
if (dateOnly === true) {
|
|
100
120
|
endDateMember.field.schemaType.name = 'date'
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// we need to explicitly set the schemaType to datetime
|
|
104
|
-
if (startDateMember?.kind == 'field') {
|
|
105
|
-
startDateMember.field.schemaType.name = 'datetime'
|
|
121
|
+
} else {
|
|
122
|
+
endDateMember.field.schemaType.name = 'datetime'
|
|
106
123
|
}
|
|
107
124
|
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
// Add custom validation to the end date field
|
|
126
|
+
if (validation?.endDate) {
|
|
127
|
+
endDateMember.field.schemaType.validation = (CustomValidation) =>
|
|
128
|
+
validation?.endDate?.(CustomValidation) as Rule
|
|
129
|
+
} else {
|
|
130
|
+
endDateMember.field.schemaType.validation = (DefaultRule) =>
|
|
131
|
+
DefaultRule.min(DefaultRule.valueOfField('startDate')) as Rule
|
|
110
132
|
}
|
|
111
|
-
}
|
|
112
133
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
134
|
+
// Add custom title to the end date field
|
|
135
|
+
if (options?.fieldTitles?.endDate) {
|
|
136
|
+
endDateMember.field.schemaType.title = options.fieldTitles.endDate
|
|
137
|
+
}
|
|
118
138
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
139
|
+
// Add custom description to the end date field
|
|
140
|
+
if (options?.fieldDescriptions?.endDate) {
|
|
141
|
+
endDateMember.field.schemaType.description = options.fieldDescriptions.endDate
|
|
142
|
+
}
|
|
122
143
|
}
|
|
123
144
|
|
|
124
145
|
// Do we have an end date set for this field?
|
|
@@ -157,6 +178,17 @@ export function RecurringDates(props: RecurringDatesProps): React.JSX.Element {
|
|
|
157
178
|
}
|
|
158
179
|
const rule = rrulestr(recurrence)
|
|
159
180
|
|
|
181
|
+
rule.options.until =
|
|
182
|
+
rule?.options?.until &&
|
|
183
|
+
datetime(
|
|
184
|
+
rule?.options?.until?.getFullYear(),
|
|
185
|
+
rule?.options?.until?.getMonth() + 1,
|
|
186
|
+
rule?.options?.until?.getDate(),
|
|
187
|
+
rule?.options?.until?.getHours(),
|
|
188
|
+
rule?.options?.until?.getMinutes(),
|
|
189
|
+
rule?.options?.until?.getSeconds(),
|
|
190
|
+
)
|
|
191
|
+
|
|
160
192
|
return (
|
|
161
193
|
<option key={recurrence} value={recurrence}>
|
|
162
194
|
{upperFirst(rule.toText())}
|
|
@@ -175,6 +207,9 @@ export function RecurringDates(props: RecurringDatesProps): React.JSX.Element {
|
|
|
175
207
|
startDate={
|
|
176
208
|
startDateMember?.kind == 'field' ? (startDateMember?.field?.value as string) : undefined
|
|
177
209
|
}
|
|
210
|
+
endDate={
|
|
211
|
+
endDateMember?.kind == 'field' ? (endDateMember?.field?.value as string) : undefined
|
|
212
|
+
}
|
|
178
213
|
dateTimeOptions={dateTimeOptions}
|
|
179
214
|
/>
|
|
180
215
|
</Stack>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {DEFAULT_DATE_FORMAT, DEFAULT_TIME_FORMAT, format} from '@sanity/util/legacyDateFormat'
|
|
2
2
|
import {upperFirst} from 'lodash'
|
|
3
3
|
import React from 'react'
|
|
4
|
-
import {rrulestr} from 'rrule'
|
|
4
|
+
import {datetime, rrulestr} from 'rrule'
|
|
5
5
|
import type {ObjectSchemaType, PreviewProps} from 'sanity'
|
|
6
6
|
|
|
7
7
|
import type {PluginConfig, RecurringDate, WithRequiredProperty} from '../types'
|
|
@@ -26,6 +26,19 @@ export function RecurringDatesPreview(props: CastPreviewProps): React.JSX.Elemen
|
|
|
26
26
|
|
|
27
27
|
const rule = rrule && rrulestr(rrule)
|
|
28
28
|
|
|
29
|
+
if (rule) {
|
|
30
|
+
rule.options.until =
|
|
31
|
+
rule?.options?.until &&
|
|
32
|
+
datetime(
|
|
33
|
+
rule?.options?.until?.getFullYear(),
|
|
34
|
+
rule?.options?.until?.getMonth() + 1,
|
|
35
|
+
rule?.options?.until?.getDate(),
|
|
36
|
+
rule?.options?.until?.getHours(),
|
|
37
|
+
rule?.options?.until?.getMinutes(),
|
|
38
|
+
rule?.options?.until?.getSeconds(),
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
const dateFormat = dateTimeOptions?.dateFormat || DEFAULT_DATE_FORMAT
|
|
30
43
|
const timeFormat = dateTimeOptions?.timeFormat || DEFAULT_TIME_FORMAT
|
|
31
44
|
|
|
@@ -8,7 +8,7 @@ import {PluginConfig, WithRequiredProperty} from '../types'
|
|
|
8
8
|
export default (
|
|
9
9
|
config: WithRequiredProperty<PluginConfig, 'defaultRecurrences'>,
|
|
10
10
|
): SchemaTypeDefinition => {
|
|
11
|
-
const {dateTimeOptions, dateOnly, validation} = config
|
|
11
|
+
const {dateTimeOptions, dateOnly, validation, fieldTitles, fieldDescriptions} = config
|
|
12
12
|
|
|
13
13
|
return defineField({
|
|
14
14
|
name: 'recurringDates',
|
|
@@ -17,7 +17,8 @@ export default (
|
|
|
17
17
|
icon: CalendarIcon,
|
|
18
18
|
fields: [
|
|
19
19
|
defineField({
|
|
20
|
-
title: 'Start Date',
|
|
20
|
+
title: fieldTitles?.startDate || 'Start Date',
|
|
21
|
+
description: fieldDescriptions?.startDate || '',
|
|
21
22
|
name: 'startDate',
|
|
22
23
|
type: dateOnly ? 'date' : 'datetime',
|
|
23
24
|
options: dateTimeOptions,
|
|
@@ -25,7 +26,8 @@ export default (
|
|
|
25
26
|
validation?.startDate ? validation.startDate(Rule) : Rule.required(),
|
|
26
27
|
}),
|
|
27
28
|
defineField({
|
|
28
|
-
title: 'End Date',
|
|
29
|
+
title: fieldTitles?.endDate || 'End Date',
|
|
30
|
+
description: fieldDescriptions?.endDate || '',
|
|
29
31
|
name: 'endDate',
|
|
30
32
|
type: dateOnly ? 'date' : 'datetime',
|
|
31
33
|
options: dateTimeOptions,
|
package/src/types.ts
CHANGED
|
@@ -10,6 +10,14 @@ export interface PluginConfig {
|
|
|
10
10
|
startDate?: (Rule: DateRule) => DateRule
|
|
11
11
|
endDate?: (Rule: DateRule) => DateRule
|
|
12
12
|
}
|
|
13
|
+
fieldTitles?: {
|
|
14
|
+
startDate?: string
|
|
15
|
+
endDate?: string
|
|
16
|
+
}
|
|
17
|
+
fieldDescriptions?: {
|
|
18
|
+
startDate?: string
|
|
19
|
+
endDate?: string
|
|
20
|
+
}
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
export type WithRequiredProperty<Type, Key extends keyof Type> = Type & {
|