sanity-plugin-recurring-dates 1.1.1 → 1.3.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,6 +1,7 @@
1
1
  import {Box, Grid, Text} from '@sanity/ui'
2
2
  import {isSameDay, isSameMonth} from 'date-fns'
3
3
  import React from 'react'
4
+
4
5
  import {CalendarDay} from './CalendarDay'
5
6
  import {WEEK_DAY_NAMES} from './constants'
6
7
  import {getWeeksOfMonth} from './utils'
@@ -13,7 +14,7 @@ interface CalendarMonthProps {
13
14
  hidden?: boolean
14
15
  }
15
16
 
16
- export function CalendarMonth(props: CalendarMonthProps) {
17
+ export function CalendarMonth(props: CalendarMonthProps): React.JSX.Element {
17
18
  return (
18
19
  <Box aria-hidden={props.hidden || false} data-ui="CalendarMonth">
19
20
  <Grid gap={1} style={{gridTemplateColumns: 'repeat(7, minmax(44px, 46px))'}}>
@@ -44,7 +45,7 @@ export function CalendarMonth(props: CalendarMonthProps) {
44
45
  selected={selected}
45
46
  />
46
47
  )
47
- })
48
+ }),
48
49
  )}
49
50
  </Grid>
50
51
  </Box>
@@ -1,5 +1,6 @@
1
- import React from 'react'
2
1
  import {TextInput} from '@sanity/ui'
2
+ import React from 'react'
3
+
3
4
  import {LazyTextInput} from '../LazyTextInput'
4
5
 
5
6
  type Props = Omit<React.ComponentProps<typeof TextInput>, 'onChange' | 'value'> & {
@@ -7,7 +8,7 @@ type Props = Omit<React.ComponentProps<typeof TextInput>, 'onChange' | 'value'>
7
8
  onChange: (year: number) => void
8
9
  }
9
10
 
10
- export const YearInput = ({onChange, ...props}: Props) => {
11
+ export const YearInput = ({onChange, ...props}: Props): React.JSX.Element => {
11
12
  const handleChange = React.useCallback(
12
13
  (event: React.FocusEvent<HTMLInputElement> | React.ChangeEvent<HTMLInputElement>) => {
13
14
  const numericValue = parseInt(event.currentTarget.value, 10)
@@ -15,7 +16,7 @@ export const YearInput = ({onChange, ...props}: Props) => {
15
16
  onChange(numericValue)
16
17
  }
17
18
  },
18
- [onChange]
19
+ [onChange],
19
20
  )
20
21
 
21
22
  return <LazyTextInput {...props} onChange={handleChange} inputMode="numeric" />
@@ -2,7 +2,13 @@ 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
4
  import {rrulestr} from 'rrule'
5
- import {ObjectInputMember, type ObjectInputProps, ObjectSchemaType, set} from 'sanity'
5
+ import {
6
+ ObjectInputMember,
7
+ type ObjectInputProps,
8
+ type ObjectSchemaType,
9
+ type Rule,
10
+ set,
11
+ } from 'sanity'
6
12
  import {Feedback} from 'sanity-plugin-utils'
7
13
 
8
14
  import type {PluginConfig, WithRequiredProperty} from '../types'
@@ -18,10 +24,10 @@ type RecurringDateObjectSchemaType = Omit<ObjectSchemaType, 'options'> & {
18
24
  options?: PluginConfig
19
25
  }
20
26
 
21
- export function RecurringDates(props: RecurringDatesProps) {
27
+ export function RecurringDates(props: RecurringDatesProps): React.JSX.Element {
22
28
  const {onChange, members, value: currentValue, schemaType, pluginConfig} = props
23
29
  const {options, title}: RecurringDateObjectSchemaType = schemaType
24
- const {defaultRecurrences, hideEndDate, hideCustom, dateTimeOptions, dateOnly} = {
30
+ const {defaultRecurrences, hideEndDate, hideCustom, dateTimeOptions, dateOnly, validation} = {
25
31
  ...pluginConfig,
26
32
  ...options,
27
33
  }
@@ -104,8 +110,19 @@ export function RecurringDates(props: RecurringDatesProps) {
104
110
  }
105
111
  }
106
112
 
113
+ // Add custom validation to the start and end date fields
114
+ if (validation?.startDate && startDateMember?.kind == 'field') {
115
+ startDateMember.field.schemaType.validation = (CustomValidation) =>
116
+ validation?.startDate?.(CustomValidation) as Rule
117
+ }
118
+
119
+ if (validation?.endDate && endDateMember?.kind == 'field') {
120
+ endDateMember.field.schemaType.validation = (CustomValidation) =>
121
+ validation?.endDate?.(CustomValidation) as Rule
122
+ }
123
+
107
124
  // Do we have an end date set for this field?
108
- const hasEndDate = currentValue && currentValue.endDate
125
+ const hasEndDate = currentValue?.endDate
109
126
 
110
127
  return (
111
128
  <Stack space={3}>
@@ -0,0 +1,55 @@
1
+ import {DEFAULT_DATE_FORMAT, DEFAULT_TIME_FORMAT, format} from '@sanity/util/legacyDateFormat'
2
+ import {upperFirst} from 'lodash'
3
+ import React from 'react'
4
+ import {rrulestr} from 'rrule'
5
+ import type {ObjectSchemaType, PreviewProps} from 'sanity'
6
+
7
+ import type {PluginConfig, RecurringDate, WithRequiredProperty} from '../types'
8
+
9
+ type CastPreviewProps = PreviewProps &
10
+ RecurringDate & {
11
+ pluginConfig: WithRequiredProperty<PluginConfig, 'defaultRecurrences'>
12
+ }
13
+
14
+ type RecurringDateObjectSchemaType = Omit<ObjectSchemaType, 'options'> & {
15
+ options?: PluginConfig
16
+ }
17
+
18
+ export function RecurringDatesPreview(props: CastPreviewProps): React.JSX.Element {
19
+ const {startDate, endDate, rrule, schemaType, pluginConfig} = props
20
+ const options: RecurringDateObjectSchemaType = schemaType?.options
21
+
22
+ const {dateTimeOptions, dateOnly} = {
23
+ ...pluginConfig,
24
+ ...options,
25
+ }
26
+
27
+ const rule = rrule && rrulestr(rrule)
28
+
29
+ const dateFormat = dateTimeOptions?.dateFormat || DEFAULT_DATE_FORMAT
30
+ const timeFormat = dateTimeOptions?.timeFormat || DEFAULT_TIME_FORMAT
31
+
32
+ const start = startDate ? new Date(startDate) : undefined
33
+ const end = endDate ? new Date(endDate) : undefined
34
+ const sameDay = start && end && start.toDateString() === end.toDateString()
35
+
36
+ let title = 'No start date'
37
+ if (dateOnly) {
38
+ title = start ? format(start, dateFormat) : 'No start date'
39
+ if (end && !sameDay) {
40
+ title += ` - ${format(end, dateFormat)}`
41
+ }
42
+ } else {
43
+ title = start ? format(start, `${dateFormat} ${timeFormat}`) : 'No start date'
44
+ if (end) {
45
+ title += ` - ${format(end, sameDay ? timeFormat : `${dateFormat} ${timeFormat}`)}`
46
+ }
47
+ }
48
+
49
+ const previewProps = {
50
+ title,
51
+ subtitle: rule && upperFirst(rule.toText()),
52
+ }
53
+
54
+ return props.renderDefault({...previewProps, ...props})
55
+ }
@@ -2,6 +2,7 @@ import {TrashIcon, WarningOutlineIcon} from '@sanity/icons'
2
2
  import {Box, Button, Card, Flex, Stack, Text} from '@sanity/ui'
3
3
  import {upperFirst} from 'lodash'
4
4
  import {useCallback} from 'react'
5
+ import React from 'react'
5
6
  import {type ObjectInputProps, unset} from 'sanity'
6
7
 
7
8
  export function RemoveEndDate({
@@ -10,7 +11,7 @@ export function RemoveEndDate({
10
11
  }: {
11
12
  title?: string
12
13
  onChange: ObjectInputProps['onChange']
13
- }) {
14
+ }): React.JSX.Element {
14
15
  // Update the RRULE field when the select changes
15
16
  const handleUnsetClick = useCallback(() => {
16
17
  onChange(unset(['endDate']))
@@ -39,7 +40,7 @@ export function RemoveEndDate({
39
40
  tone="critical"
40
41
  text={<>Remove end date</>}
41
42
  onClick={handleUnsetClick}
42
- width="100%"
43
+ width="fill"
43
44
  />
44
45
  </Stack>
45
46
  </Flex>
@@ -1,29 +1,36 @@
1
- import {defineField} from 'sanity'
1
+ import {CalendarIcon} from '@sanity/icons'
2
+ import {defineField, SchemaTypeDefinition} from 'sanity'
2
3
 
3
4
  import {RecurringDates} from '../components/RecurringDate'
5
+ import {RecurringDatesPreview} from '../components/RecurringDatesPreview'
4
6
  import {PluginConfig, WithRequiredProperty} from '../types'
5
7
 
6
- export default (config: WithRequiredProperty<PluginConfig, 'defaultRecurrences'>) => {
7
- const {dateTimeOptions, dateOnly} = config
8
+ export default (
9
+ config: WithRequiredProperty<PluginConfig, 'defaultRecurrences'>,
10
+ ): SchemaTypeDefinition => {
11
+ const {dateTimeOptions, dateOnly, validation} = config
8
12
 
9
13
  return defineField({
10
14
  name: 'recurringDates',
11
15
  title: 'Dates',
12
16
  type: 'object',
17
+ icon: CalendarIcon,
13
18
  fields: [
14
19
  defineField({
15
20
  title: 'Start Date',
16
21
  name: 'startDate',
17
22
  type: dateOnly ? 'date' : 'datetime',
18
23
  options: dateTimeOptions,
19
- validation: (Rule) => Rule.required(),
24
+ validation: (Rule) =>
25
+ validation?.startDate ? validation.startDate(Rule) : Rule.required(),
20
26
  }),
21
27
  defineField({
22
28
  title: 'End Date',
23
29
  name: 'endDate',
24
30
  type: dateOnly ? 'date' : 'datetime',
25
31
  options: dateTimeOptions,
26
- validation: (Rule) => Rule.min(Rule.valueOfField('startDate')),
32
+ validation: (Rule) =>
33
+ validation?.endDate ? validation.endDate(Rule) : Rule.min(Rule.valueOfField('startDate')),
27
34
  }),
28
35
  defineField({
29
36
  title: 'Recurring event',
@@ -39,6 +46,14 @@ export default (config: WithRequiredProperty<PluginConfig, 'defaultRecurrences'>
39
46
  ],
40
47
  components: {
41
48
  input: (props) => RecurringDates({...props, pluginConfig: config}),
49
+ preview: (props) => RecurringDatesPreview({...props, pluginConfig: config}),
50
+ },
51
+ preview: {
52
+ select: {
53
+ startDate: 'startDate',
54
+ endDate: 'endDate',
55
+ rrule: 'rrule',
56
+ },
42
57
  },
43
58
  })
44
59
  }
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type {DatetimeOptions, ObjectDefinition} from 'sanity'
1
+ import type {DateRule, DatetimeOptions, ObjectDefinition} from 'sanity'
2
2
 
3
3
  export interface PluginConfig {
4
4
  defaultRecurrences?: string[]
@@ -6,6 +6,10 @@ export interface PluginConfig {
6
6
  hideCustom?: boolean
7
7
  dateTimeOptions?: DatetimeOptions
8
8
  dateOnly?: boolean
9
+ validation?: {
10
+ startDate?: (Rule: DateRule) => DateRule
11
+ endDate?: (Rule: DateRule) => DateRule
12
+ }
9
13
  }
10
14
 
11
15
  export type WithRequiredProperty<Type, Key extends keyof Type> = Type & {
@@ -28,7 +32,7 @@ declare module 'sanity' {
28
32
  }
29
33
 
30
34
  export interface RecurringDate {
31
- rrule: string
32
- startDate: string
33
- endDate: string
35
+ rrule?: string
36
+ startDate?: string
37
+ endDate?: string
34
38
  }