Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/dataviews/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

## Unreleased

### Enhancements

- Improve docs for Edit component. [#73202](https://github.com/WordPress/gutenberg/pull/73202)
- Field API: introduce the `format` prop to format the `date` field type. [#72999](https://github.com/WordPress/gutenberg/pull/72999)

## 10.3.0 (2025-11-12)

### Enhancements

- Improve docs for Edit component. [#73202](https://github.com/WordPress/gutenberg/pull/73202)
- DataForm: add new details layout. [#72355](https://github.com/WordPress/gutenberg/pull/72355)
- DatViews list layout: remove link variant from primary actions's button. [#72920](https://github.com/WordPress/gutenberg/pull/72920)
- DataForm: simplify form normalization. [#72848](https://github.com/WordPress/gutenberg/pull/72848)
Expand Down
24 changes: 24 additions & 0 deletions packages/dataviews/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,30 @@ Example:
}
```

### `format`

Display format configuration for fields. Currently supported for date fields. This configuration affects how the field is displayed in the `render` method, the `Edit` control, and filter controls.

- Type: `object`.
- Optional.
- Properties:
- `date`: The format string using PHP date format (e.g., 'F j, Y' for 'March 10, 2023'). Optional, defaults to WordPress "Date Format" setting.
- `weekStartsOn`: Specifies the first day of the week for calendar controls. One of `'sunday'`, `'monday'`, `'tuesday'`, `'wednesday'`, `'thursday'`, `'friday'`, `'saturday'`. Optional, defaults to WordPress "Week Starts On" setting.

Example:

```js
{
id: 'publishDate',
type: 'date',
label: 'Publish Date',
format: {
date: 'F j, Y',
weekStartsOn: 'monday',
},
}
```

## Form Field API

### `id`
Expand Down
12 changes: 11 additions & 1 deletion packages/dataviews/src/components/dataviews-filters/filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { __, sprintf } from '@wordpress/i18n';
import { useRef, createInterpolateElement } from '@wordpress/element';
import { closeSmall } from '@wordpress/icons';
import { dateI18n, getDate } from '@wordpress/date';

const ENTER = 'Enter';
const SPACE = ' ';
Expand Down Expand Up @@ -498,7 +499,16 @@ export default function Filter( {
const field = fields.find( ( f ) => f.id === filter.field );
let label = filterInView.value;

if ( field?.type === 'datetime' && typeof label === 'string' ) {
if ( field?.type === 'date' && typeof label === 'string' ) {
try {
const dateValue = parseDateTime( label );
if ( dateValue !== null ) {
label = dateI18n( field.format.date, getDate( label ) );
}
} catch ( e ) {
label = filterInView.value;
}
} else if ( field?.type === 'datetime' && typeof label === 'string' ) {
try {
const dateValue = parseDateTime( label );
if ( dateValue !== null ) {
Expand Down
30 changes: 24 additions & 6 deletions packages/dataviews/src/dataform-controls/date.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import type {
NormalizedField,
} from '../types';
import getCustomValidity from './utils/get-custom-validity';
import { weekStartsOnToNumber } from '../utils/week-starts-on';

const { DateCalendar, DateRangeCalendar } = unlock( componentsPrivateApis );

Expand Down Expand Up @@ -257,11 +258,24 @@ function CalendarDateControl< Item >( {
hideLabelFromVision,
validity,
}: DataFormControlProps< Item > ) {
const { id, label, setValue, getValue, isValid } = field;
const {
id,
type,
label,
setValue,
getValue,
isValid,
format: fieldFormat,
} = field;
const [ selectedPresetId, setSelectedPresetId ] = useState< string | null >(
null
);

let weekStartsOn;
if ( type === 'date' ) {
weekStartsOn = weekStartsOnToNumber( fieldFormat.weekStartsOn );
}

const fieldValue = getValue( { item: data } );
const value = typeof fieldValue === 'string' ? fieldValue : undefined;
const [ calendarMonth, setCalendarMonth ] = useState< Date >( () => {
Expand Down Expand Up @@ -320,7 +334,6 @@ function CalendarDateControl< Item >( {

const {
timezone: { string: timezoneString },
l10n: { startOfWeek },
} = getSettings();

const displayLabel = isValid?.required
Expand Down Expand Up @@ -396,7 +409,7 @@ function CalendarDateControl< Item >( {
month={ calendarMonth }
onMonthChange={ setCalendarMonth }
timeZone={ timezoneString || undefined }
weekStartsOn={ startOfWeek }
weekStartsOn={ weekStartsOn }
/>
</VStack>
</BaseControl>
Expand All @@ -411,7 +424,7 @@ function CalendarDateRangeControl< Item >( {
hideLabelFromVision,
validity,
}: DataFormControlProps< Item > ) {
const { id, label, getValue, setValue } = field;
const { id, type, label, getValue, setValue, format: fieldFormat } = field;
let value: DateRange;
const fieldValue = getValue( { item: data } );
if (
Expand All @@ -422,6 +435,11 @@ function CalendarDateRangeControl< Item >( {
value = fieldValue as DateRange;
}

let weekStartsOn;
if ( type === 'date' ) {
weekStartsOn = weekStartsOnToNumber( fieldFormat.weekStartsOn );
}

const onChangeCallback = useCallback(
( newValue: DateRange ) => {
onChange(
Expand Down Expand Up @@ -521,7 +539,7 @@ function CalendarDateRangeControl< Item >( {
[ value, updateDateRange ]
);

const { timezone, l10n } = getSettings();
const { timezone } = getSettings();

const displayLabel = field.isValid?.required
? `${ label } (${ __( 'Required' ) })`
Expand Down Expand Up @@ -609,7 +627,7 @@ function CalendarDateRangeControl< Item >( {
month={ calendarMonth }
onMonthChange={ setCalendarMonth }
timeZone={ timezone.string || undefined }
weekStartsOn={ l10n.startOfWeek }
weekStartsOn={ weekStartsOn }
/>
</VStack>
</BaseControl>
Expand Down
16 changes: 11 additions & 5 deletions packages/dataviews/src/field-types/date.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
import { dateI18n, getDate, getSettings } from '@wordpress/date';
import { dateI18n, getDate } from '@wordpress/date';

/**
* Internal dependencies
Expand All @@ -24,9 +24,6 @@ import {
OPERATOR_BETWEEN,
} from '../constants';

const getFormattedDate = ( dateToDisplay: string | null ) =>
dateI18n( getSettings().formats.date, getDate( dateToDisplay ) );

function sort( a: any, b: any, direction: SortDirection ) {
const timeA = new Date( a ).getTime();
const timeB = new Date( b ).getTime();
Expand All @@ -51,7 +48,16 @@ export default {
return '';
}

return getFormattedDate( value );
// Not all fields have format, but date fields do.
//
// At runtime, this method will never be called for non-date fields.
// However, the type system does not know this, so we need to check it.
// There's an opportunity here to improve the type system.
if ( field.type !== 'date' ) {
return '';
}

return dateI18n( field.format.date, getDate( value ) );
},
enableSorting: true,
filterBy: {
Expand Down
69 changes: 67 additions & 2 deletions packages/dataviews/src/stories/field-types.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -832,14 +832,53 @@ export const DateComponent = ( {
type,
Edit,
asyncElements,
formatDate,
formatWeekStartsOn,
}: {
type: PanelTypes;
Edit: ControlTypes;
asyncElements: boolean;
formatDate?: string;
formatWeekStartsOn?:
| 'sunday'
| 'monday'
| 'tuesday'
| 'wednesday'
| 'thursday'
| 'friday'
| 'saturday';
} ) => {
const dateFields = useMemo(
() => fields.filter( ( field ) => field.type === 'date' ),
[]
() =>
fields
.filter( ( field ) => field.type === 'date' )
.map( ( field ) => {
if ( formatDate || formatWeekStartsOn !== undefined ) {
const format: {
date?: string;
weekStartsOn?:
| 'sunday'
| 'monday'
| 'tuesday'
| 'wednesday'
| 'thursday'
| 'friday'
| 'saturday';
} = {};
if ( formatDate ) {
format.date = formatDate;
}
if ( formatWeekStartsOn !== undefined ) {
format.weekStartsOn = formatWeekStartsOn;
}
return {
...field,
format,
};
}
return field;
} ),
[ formatDate, formatWeekStartsOn ]
);

return (
Expand All @@ -852,6 +891,32 @@ export const DateComponent = ( {
);
};
DateComponent.storyName = 'date';
DateComponent.args = {
formatDate: '',
formatWeekStartsOn: undefined,
};
DateComponent.argTypes = {
formatDate: {
control: 'text',
description:
'Custom PHP date format string (e.g., "F j, Y" for "November 6, 2010"). Leave empty to use WordPress default.',
},
formatWeekStartsOn: {
control: 'select',
options: {
Default: undefined,
Sunday: 'sunday',
Monday: 'monday',
Tuesday: 'tuesday',
Wednesday: 'wednesday',
Thursday: 'thursday',
Friday: 'friday',
Saturday: 'saturday',
},
description:
'Day that the week starts on. Leave as Default to use WordPress default.',
},
};

export const EmailComponent = ( {
type,
Expand Down
53 changes: 53 additions & 0 deletions packages/dataviews/src/test/normalize-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,57 @@ describe( 'normalizeFields: default getValue', () => {
} );
} );
} );

describe( 'format normalization', () => {
it( 'applies default format when not provided for date fields', () => {
const fields: Field< {} >[] = [
{
id: 'publishDate',
type: 'date',
},
];
const normalizedFields = normalizeFields( fields );
expect( normalizedFields[ 0 ].format ).toBeDefined();
expect( normalizedFields[ 0 ].format.date ).toBeDefined();
expect( typeof normalizedFields[ 0 ].format.date ).toBe( 'string' );
expect( normalizedFields[ 0 ].format.weekStartsOn ).toBeDefined();
expect( typeof normalizedFields[ 0 ].format.weekStartsOn ).toBe(
'string'
);
} );

it( 'preserves custom format when provided', () => {
const fields: Field< {} >[] = [
{
id: 'publishDate',
type: 'date',
format: {
date: 'F j, Y',
weekStartsOn: 'monday',
},
},
];
const normalizedFields = normalizeFields( fields );
expect( normalizedFields[ 0 ].format.date ).toBe( 'F j, Y' );
expect( normalizedFields[ 0 ].format.weekStartsOn ).toBe(
'monday'
);
} );

it( 'adds empty format for non-date field types', () => {
const fields: Field< {} >[] = [
{
id: 'title',
type: 'text',
},
{
id: 'count',
type: 'integer',
},
];
const normalizedFields = normalizeFields( fields );
expect( normalizedFields[ 0 ].format ).toEqual( {} );
expect( normalizedFields[ 1 ].format ).toEqual( {} );
} );
} );
} );
Loading
Loading