Skip to content
Draft
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
30 changes: 30 additions & 0 deletions packages/dataviews/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,36 @@ Example:
}
```

### `children`

Defines the type of the elements of the `array` field type.

- Type: `'text' | Field<any>[]`
- `'text'`: the elements of the array are of `text` type.
- `Field<any>[]`: a list of children fields.
- Optional.

If `children` is not provided for an `array` field type, the elements of the array will be of `text` type.

Example:

```js
{
id: 'arrayWithChildren',
type: 'array',
children: [
{
id: 'child1',
type: 'text'
},
{
id: 'child2',
type: 'number'
}
]
}
```

### `sort`

Function to sort the records.
Expand Down
209 changes: 208 additions & 1 deletion packages/dataviews/src/components/dataform/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ const ValidationComponent = ( {
password: string;
toggle?: boolean;
toggleGroup?: string;
arrayWithChildren: Record< string, unknown >[];
};

const [ post, setPost ] = useState< ValidatedItem >( {
Expand All @@ -546,9 +547,13 @@ const ValidationComponent = ( {
categories: [ 'astronomy' ],
countries: [ 'us' ],
customEdit: 'custom control',
password: 'secretpassword123',
password: 'secretPassword123',
toggle: undefined,
toggleGroup: undefined,
arrayWithChildren: [
{ day: 'monday', openingHours: 4 },
{ day: 'tuesday', openingHours: 6 },
],
} );

const customTextRule = ( value: ValidatedItem ) => {
Expand Down Expand Up @@ -638,6 +643,34 @@ const ValidationComponent = ( {
return null;
};

const customNestedTextRule = ( value: any ) => {
if ( ! /^[a-zA-Z ]+$/.test( value.day ) ) {
return 'Value must only contain letters and spaces.';
}

return null;
};
const customNestedIntegerRule = ( value: any ) => {
if ( value.openingHours % 2 !== 0 ) {
return 'Integer must be an even number.';
}

return null;
};
const customNestedParentRule = ( value: ValidatedItem ) => {
if (
value.arrayWithChildren.some(
( item, index ) =>
value.arrayWithChildren.findIndex(
( otherItem ) => otherItem.day === item.day
) !== index
)
) {
return 'There cannot be repeated day values.';
}
return null;
};

const customPasswordRule = ( value: ValidatedItem ) => {
if ( value.password.length < 8 ) {
return 'Password must be at least 8 characters long.';
Expand Down Expand Up @@ -836,6 +869,35 @@ const ValidationComponent = ( {
custom: maybeCustomRule( customToggleGroupRule ),
},
},
{
id: 'arrayWithChildren',
type: 'array',
label: 'Array with children',
children: [
{
id: 'day',
label: 'Day',
type: 'text',
isValid: {
required,
custom: custom ? customNestedTextRule : undefined,
},
},
{
id: 'openingHours',
label: 'Opening Hours',
type: 'integer',
isValid: {
required,
custom: custom ? customNestedIntegerRule : undefined,
},
},
],
isValid: {
required,
custom: custom ? customNestedParentRule : undefined,
},
},
];

const form = {
Expand All @@ -855,6 +917,7 @@ const ValidationComponent = ( {
'countries',
'toggle',
'toggleGroup',
'arrayWithChildren',
'password',
'customEdit',
],
Expand Down Expand Up @@ -1527,6 +1590,150 @@ export const LayoutMixed = {
render: LayoutMixedComponent,
};

const DynamicDataComponent = () => {
type DynamicProduct = {
name: string;
cost: number;
quantity: number;
};
type DynamicData = {
productList: DynamicProduct[];
totalAmount: number;
};

const initialData = {
productList: [
{
name: 'hair protection oil',
cost: 10,
quantity: 5,
},
{
name: 'hair strength shampoo',
cost: 20,
quantity: 3,
},
],
totalAmount: 110,
};

const [ data, setData ] = useState< DynamicData >( initialData );

const form: Form = {
layout: { type: 'card' },
fields: [
{
id: 'cardWithRegular',
children: [
{
id: 'productListAsRegular',
label: 'Product list',
layout: { type: 'regular' },
children: [
'productList',
{
id: 'totalAmount',
label: 'Total Amount',
layout: { type: 'panel' },
},
],
},
],
},
{
id: 'cardWithPanel',
children: [
{
id: 'productListAsPanel',
label: 'Product list',
layout: { type: 'panel', openAs: 'modal' },
summary: 'productListSummary',
children: [
'productList',
{
id: 'totalAmount',
label: 'Total Amount',
layout: { type: 'panel' },
},
],
},
],
},
],
};

const _fields: Field< DynamicData >[] = [
{
id: 'productList',
label: 'Product List',
type: 'array',
Edit: {
control: 'table',
actions: {
delete: 'Remove product',
add: 'Add product',
},
},
children: [
{
id: 'name',
label: 'Name',
type: 'text',
},
{
id: 'cost',
label: 'Cost',
type: 'integer',
},
{
id: 'quantity',
label: 'Quantity',
type: 'integer',
},
],
},
{
id: 'productListSummary',
label: 'Products',
type: 'text',
getValue: ( { item } ) =>
item.productList
.map( ( product ) => product.name )
.join( ', ' ),
},
{
id: 'totalAmount',
label: 'Total Amount',
type: 'integer',
readOnly: true,
},
];

return (
<DataForm< DynamicData >
data={ data }
form={ form }
fields={ _fields }
onChange={ ( edits ) => {
setData( ( prev ) => {
const updated = { ...prev, ...edits };
return {
...updated,
totalAmount: updated.productList.reduce(
( acc, item ) => acc + item.cost * item.quantity,
0
),
};
} );
} }
/>
);
};

export const DynamicData = {
render: DynamicDataComponent,
};

export const Validation = {
render: ValidationComponent,
argTypes: {
Expand Down
12 changes: 12 additions & 0 deletions packages/dataviews/src/components/dataviews/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,15 @@
padding-inline-end: $grid-unit-30;
}
}

// This is part of the field-types array's render.
.dataviews-array-list > ul {
list-style-type: none;
padding-inline-start: 0;
}

// When an array is nested within another array
// we do want to provide some padding to visually see the difference.
.dataviews-array-list > ul .dataviews-array-list > ul {
padding-inline-start: revert;
}
16 changes: 16 additions & 0 deletions packages/dataviews/src/dataform-controls/date.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.dataviews-controls__date {
// Hide the native date picker icon since we're using our own calendar.
input[type="date"]::-webkit-inner-spin-button,
input[type="date"]::-webkit-calendar-picker-indicator {
display: none;
-webkit-appearance: none;
}
}

.dataviews-controls__date-preset {
border: 1px solid #ddd;

&:active {
background-color: $black;
}
}
4 changes: 4 additions & 0 deletions packages/dataviews/src/dataform-controls/datetime.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.dataviews-controls__datetime {
border: none;
padding: 0;
}
11 changes: 11 additions & 0 deletions packages/dataviews/src/dataform-controls/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import toggle from './toggle';
import textarea from './textarea';
import toggleGroup from './toggle-group';
import array from './array';
import table from './table';
import color from './color';
import password from './password';

Expand All @@ -35,6 +36,7 @@ interface FormControls {

const FORM_CONTROLS: FormControls = {
array,
table,
checkbox,
color,
datetime,
Expand Down Expand Up @@ -89,6 +91,15 @@ export function getControl< Item >(
return getControlByType( 'select' );
}

// Use array-table control for array fields with children
if (
field.type === 'array' &&
field.children &&
field.children.length > 0
) {
return getControlByType( 'table' );
}

if ( typeof fieldTypeDefinition.Edit === 'string' ) {
return getControlByType( fieldTypeDefinition.Edit );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.dataviews-controls__relative-date-number,
.dataviews-controls__relative-date-unit {
flex: 1 1 50%;
}
30 changes: 4 additions & 26 deletions packages/dataviews/src/dataform-controls/style.scss
Original file line number Diff line number Diff line change
@@ -1,26 +1,4 @@
.dataviews-controls__datetime {
border: none;
padding: 0;
}

.dataviews-controls__relative-date-number,
.dataviews-controls__relative-date-unit {
flex: 1 1 50%;
}

.dataviews-controls__date {
// Hide the native date picker icon since we're using our own calendar.
input[type="date"]::-webkit-inner-spin-button,
input[type="date"]::-webkit-calendar-picker-indicator {
display: none;
-webkit-appearance: none;
}
}

.dataviews-controls__date-preset {
border: 1px solid #ddd;

&:active {
background-color: $black;
}
}
@import "date.scss";
@import "datetime.scss";
@import "relative-date-control.scss";
@import "table.scss";
Loading
Loading