Skip to content

Commit

Permalink
Merge pull request #180 from wix-incubator/tyv/order_with_layout
Browse files Browse the repository at this point in the history
order with layout option
  • Loading branch information
tyv authored Dec 15, 2022
2 parents 3e08d1a + 04c4880 commit 6dd649f
Show file tree
Hide file tree
Showing 22 changed files with 3,560 additions and 11,976 deletions.
5 changes: 4 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,8 @@
"dist"
],
"author": "AutoViews Team (https://github.com/wix-incubator/autoviews)",
"license": "MIT"
"license": "MIT",
"devDependencies": {
"@wixc3/react-board": "^2.1.3"
}
}
134 changes: 134 additions & 0 deletions packages/core/src/_codux/boards/layout.board.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React from 'react';
import {createBoard} from '@wixc3/react-board';

import {
AutoFields,
AutoView,
ComponentsRepo,
CoreSchemaMetaSchema,
createUISchema,
RepositoryProvider,
UISchema,
getHints,
orderToTemplateAreas
} from '../../';

const baseRepo = new ComponentsRepo('BaseRepo')
.register('object', {
name: 'MyObject',
component: AutoFields
})
.register('string', {
name: 'MyStringComponent',
component: props => <span>{props.data}</span>
})
.register('number', {
name: 'MyNumberComponent',
component: props => <span>{props.data}</span>
})
.register('boolean', {
name: 'MyBooleanComponent',
component: props => <span>{props.data ? 'online' : 'offline'}</span>
});

const repo = baseRepo
.clone('LayoutRepo')
.addWrapper(
(item, props) => {
const {order} = getHints(props.uiSchema, props.pointer);
return (
<>
<style>
{`
.root {
box-sizing: border-box;
width: 100%;
display: grid;
border: 1px solid red;
grid-template-columns: auto;
grid-template-rows: 1fr;
gap: 1rem;
align-items: stretch;
}
.child {
padding: 5px;
border: 1px solid gray;
border-radius: 5px;
}
`}
</style>
<div
className="root"
style={{gridTemplateAreas: orderToTemplateAreas(order)}}
>
{item}
</div>
</>
);
},
{include: ['MyObject']}
)
.addWrapper(
(item, props) => (
<div
style={{gridArea: props.field}}
className="child"
>
{props.field}
:
{item}
</div>
),
{exclude: ['MyObject']}
);

export const userUISchema: UISchema = createUISchema(
{},
{
'': {
order: [
['age', '.', '.'],
['age', 'active', 'login']
]
}
}
);

export const data = {
login: 'johondoe',
age: 21,
active: true
};

const schema: CoreSchemaMetaSchema = {
type: 'object',
properties: {
login: {
type: 'string'
},
age: {
type: 'number'
},
active: {
type: 'boolean'
}
}
};

export default createBoard({
name: 'layout',
Board: () => (
<RepositoryProvider components={repo}>
<AutoView
schema={schema as CoreSchemaMetaSchema}
data={data}
uiSchema={userUISchema}
/>
</RepositoryProvider>
),
environmentProps: {
canvasWidth: 264,
canvasHeight: 157
}
});
4 changes: 2 additions & 2 deletions packages/core/src/auto-view/auto-headers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
ObjectSchemaAsArrayRules
} from '../utils';
import {JSONPointer} from '../repository';
import {CoreSchemaMetaSchema} from '../models';
import {CoreSchemaMetaSchema, flatUnique} from '../models';

import {getHints} from './utils';
import {AutoViewProps} from './auto-view';
Expand Down Expand Up @@ -61,7 +61,7 @@ export const AutoHeaders = (props: AutoHeadersProps) => {
);

const rules: ObjectSchemaAsArrayRules = {
order,
order: flatUnique(order),
pick: props.pick,
omit: props.omit ?? hidden
};
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/auto-view/default-items.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import {allFields, buildJsonPointer, filterAndOrderFields} from '../utils';
import {CoreSchemaMetaSchema} from '../models';
import {CoreSchemaMetaSchema, flatUnique} from '../models';

import {AutoView, AutoViewProps} from './auto-view';
import {getHints} from './utils';
Expand Down Expand Up @@ -35,7 +35,7 @@ export function autoFieldsProps(
), // if schema has additionalProperties, take fields from `data`
pick,
omit ?? hidden,
order
flatUnique(order)
);

return fields.map(field => ({
Expand Down
34 changes: 33 additions & 1 deletion packages/core/src/auto-view/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ComponentOptions, getDefaultHints, UIHints, UISchema} from '../models';
import {createUISchemaAccessor} from '../models/UISchema';
import {createUISchemaAccessor, isOrderFlat} from '../models/UISchema';
import {RepoName} from '../repository';

export function getHints(uiSchema?: UISchema, pointer = ''): UIHints {
Expand Down Expand Up @@ -27,3 +27,35 @@ export function getComponentOptions(
).getComponentOptions(repo);
return compOptions && compOptions.options;
}

const stringifyRow = (arr: string[]) => `"${arr.join(' ')}"` + '\n';
export function orderToTemplateAreas(order: UIHints['order']): string {
if (!order) return '';

if (isOrderFlat(order)) {
return order.map(field => [`"${field}"`]).join('\n');
}

const columnsCount = Math.max(
...order.map(row => (typeof row === 'string' ? 1 : row.length))
);

const result = order.reduce<string>((acc, r) => {
if (typeof r === 'string') {
return acc + stringifyRow(new Array(columnsCount).fill(r));
}

const row = [...r];

if (row.length < columnsCount) {
const originalRowLength = row.length;
row.length = columnsCount;
row.fill('.', originalRowLength);
}

return acc + stringifyRow(row);
}, '');

// .slice removes last '\n'
return result.slice(0, -1);
}
3 changes: 2 additions & 1 deletion packages/core/src/models/UISchema/UISchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ export type AccessorType =
| 'array'
| 'object';

export type Order = Array<string | string[]>;
export interface UIHints {
order?: string[];
order?: Order;
hidden?: string[];
uiGroups?: UIGroup[];
autoFocus?: JSONPointer;
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/models/UISchema/accessors/object.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ComponentOptions,
Order,
UIGroup,
UIHintsOverrides,
UISchema
Expand Down Expand Up @@ -27,8 +28,8 @@ export interface ObjectAccessorInterface extends BaseAccessorInterface {
getGroupPosition(groupName: string): number;
isPropertyInGroup(groupName: string, prop: string): boolean;

setOrder(iterator: (order: string[]) => string[]): this;
getOrder(): string[] | undefined;
setOrder(iterator: (order: Order) => Order): this;
getOrder(): Order | undefined;
addPropertyToOrder(propertyName: string, position?: number): this;
removePropertyFromOrder(propertyName: string): this;
setPropertyPositionInOrder(propertyName: string, position: number): this;
Expand Down Expand Up @@ -202,7 +203,7 @@ export class ObjectAccessor
return targetGroup ? targetGroup.fields.indexOf(property) >= 0 : false;
}

public setOrder(iterator: (order: string[]) => string[]) {
public setOrder(iterator: (order: Order) => Order) {
ensureUIHints(this.uiSchemaClone.hints, this.path);
const hints = this.getHints();
const {order = []} = hints;
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/models/UISchema/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,13 @@ export function getDefaultHints(): UIHints {
uiGroups: []
};
}

export function isOrderFlat(order: UIHints['order']): order is string[] {
if (!order) return false;
return order.every(item => typeof item === 'string');
}

export function flatUnique(order: UIHints['order']): string[] {
if (!order) return [];
return [...new Set(order.flat())];
}
26 changes: 26 additions & 0 deletions packages/core/tests/order-to-template-areas.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {orderToTemplateAreas} from '../src';

describe('Converting order into grid-template-areas', () => {
it('should convert array of strings', async () => {
expect(orderToTemplateAreas(['a', 'b', 'c'])).toEqual('"a"\n"b"\n"c"');
});
it('should fill gaps', async () => {
expect(orderToTemplateAreas([['a', 'b', 'b'], 'c', 'd'])).toEqual(
'"a b b"\n"c c c"\n"d d d"'
);
});
it('should respect empty cell and fill to right with empty cell', async () => {
expect(
orderToTemplateAreas([
['a', 'b', 'b'],
['c', 'c', '.'],
['.', 'd']
])
).toEqual('"a b b"\n"c c ."\n". d ."');
});
it('should respect fields areas on a few rows', async () => {
expect(
orderToTemplateAreas([['a', 'b', 'b'], ['c', 'b', 'b'], 'd'])
).toEqual('"a b b"\n"c b b"\n"d d d"');
});
});
41 changes: 40 additions & 1 deletion packages/core/tests/ui-schema/ui-schema.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,45 @@ describe('uiSchema', () => {
);
});

it('should order with layout-order definition', () => {
const initial = createUISchema();
const changed = createUISchemaAccessor(initial, '')
.setHints(() => ({
order: [
['second', 'third', 'third'],
['.', 'first', 'first']
]
}))
.get();

render(
<RepositoryProvider components={repo}>
<AutoView
schema={objectSchema}
uiSchema={changed}
data={{first: '', second: '', third: ''}}
/>
</RepositoryProvider>
);

const fieldset = screen.getByTestId('#FIELDSET');
const textboxes = within(fieldset).getAllByRole('textbox');
const [second, third, first] = Array.from(textboxes);

expect(first).toHaveAttribute(
'data-automation-id',
'/first#NATIVE_TEXT_INPUT'
);
expect(second).toHaveAttribute(
'data-automation-id',
'/second#NATIVE_TEXT_INPUT'
);
expect(third).toHaveAttribute(
'data-automation-id',
'/third#NATIVE_TEXT_INPUT'
);
});

it('should order fields in array of objects', () => {
const initial = createUISchema();
const changed = createUISchemaAccessor(initial, '/items')
Expand Down Expand Up @@ -491,7 +530,7 @@ describe('uiSchema', () => {
);
});

it(`should not throw if order contains field which uiSchema doesn't have`, () => {
it(`should not throw if order contains field which JSONSchema doesn't have`, () => {
const initial = createUISchema();
const changed = createUISchemaAccessor(initial, '')
.setHints(() => ({order: ['unknown']}))
Expand Down
2 changes: 1 addition & 1 deletion website/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ import {showcaseHomepage} from '@site/src/examples/showcase';

4. **Your UI Schema**

The UI Schema adds another layer of hints that can be used by components to fine tune the rendered UI. UI Schema includes field ordering, grouping, component selection, field hiding and auto focus hints.
The UI Schema adds another layer of hints that can be used by components to fine tune the rendered UI. UI Schema includes field ordering/layouting, grouping, component selection, field hiding and auto focus hints.

More about [the UI Schema](./docs/entities/ui-schema)
Loading

0 comments on commit 6dd649f

Please sign in to comment.