Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react): add slot ability #4259

Open
wants to merge 1 commit into
base: formily_next
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions packages/json-schema/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
SchemaTypes,
SchemaKey,
ISchemaTransformerOptions,
Slot,
} from './types'
import { IFieldFactoryProps } from '@formily/core'
import { map, each, isFn, instOf, FormPath, isStr } from '@formily/shared'
Expand Down Expand Up @@ -180,6 +181,8 @@ export class Schema<

['x-compile-omitted']?: string[];

['x-slot-node']?: Slot;

[key: `x-${string | number}` | symbol]: any

_isJSONSchemaObject = true
Expand Down
5 changes: 5 additions & 0 deletions packages/json-schema/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ export interface ISchemaTransformerOptions {
scope?: IScopeContext
}

export type Slot = {
target: string
isRenderProp?: boolean
}

export type Stringify<P extends { [key: string]: any }> = {
/**
* Use `string & {}` instead of string to keep Literal Type for ISchema#component and ISchema#decorator
Expand Down
121 changes: 121 additions & 0 deletions packages/react/docs/api/components/SchemaField.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,124 @@ export default () => (
</FormProvider>
)
```

## JSON Schema ReactNode Prop Use Case (x-slot-node)

Reference [Slot](https://react.formilyjs.org/api/shared/schema#slot)

```tsx
import React from 'react'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Input, Tag } from 'antd'
import { CheckCircleTwoTone, CloseCircleOutlined } from '@ant-design/icons'

const form = createForm()

const SchemaField = createSchemaField({
components: {
Input,
CheckCircleTwoTone,
CloseCircleOutlined,
},
})

export default () => (
<FormProvider form={form}>
<SchemaField
components={{
Tag,
}}
schema={{
type: 'object',
properties: {
tag: {
'x-slot-node': {
target: 'input.x-component-props.prefix',
},
'x-component': 'Tag',
'x-component-props': {
children: 'www.',
},
},
tag2: {
'x-slot-node': {
target: 'input.x-component-props.suffix',
},
'x-component': 'Tag',
'x-component-props': {
children: '.com',
},
},
icon: {
'x-slot-node': {
target: 'input.x-component-props.addonAfter',
},
'x-component':
'{{$form.values.input?.length > 5 ? "CheckCircleTwoTone" : "CloseCircleOutlined"}}',
},
input: {
type: 'string',
'x-component': 'Input',
'x-component-props': {},
},
},
}}
></SchemaField>
</FormProvider>
)
```

## JSON Schema Render Prop Use Case (x-slot-node & isRenderProp)

Reference [Slot](https://react.formilyjs.org/api/shared/schema#slot)

```tsx
import React from 'react'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Rate } from 'antd'
import { DollarOutlined } from '@ant-design/icons'

const form = createForm()

const SchemaField = createSchemaField({
components: {
DollarOutlined,
},
})

export default () => (
<FormProvider form={form}>
<SchemaField
components={{
Rate,
}}
schema={{
type: 'object',
properties: {
icon: {
'x-slot-node': {
target: 'rate.x-component-props.character',
isRenderProp: true,
},
'x-component': 'DollarOutlined',
'x-component-props': {
rotate: '{{ $slotArgs[0].value * 45 }}',
style: {
fontSize: '50px',
},
},
},
rate: {
'x-component': 'Rate',
'x-component-props': {
defaultValue: 3,
},
},
},
}}
></SchemaField>
</FormProvider>
)
```
120 changes: 120 additions & 0 deletions packages/react/docs/api/components/SchemaField.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,123 @@ export default () => (
</FormProvider>
)
```

## JSON Schema ReactNode Prop 用例 (x-slot-node)

参考[Slot](https://react.formilyjs.org/zh-CN/api/shared/schema#slot)

```tsx
import React from 'react'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Input, Tag } from 'antd'
import { CheckCircleTwoTone } from '@ant-design/icons'

const form = createForm()

const SchemaField = createSchemaField({
components: {
Input,
CheckCircleTwoTone,
},
})

export default () => (
<FormProvider form={form}>
<SchemaField
components={{
Tag,
}}
schema={{
type: 'object',
properties: {
tag: {
'x-slot-node': {
target: 'input.x-component-props.prefix',
},
'x-component': 'Tag',
'x-component-props': {
children: 'www.',
},
},
tag2: {
'x-slot-node': {
target: 'input.x-component-props.suffix',
},
'x-component': 'Tag',
'x-component-props': {
children: '.com',
},
},
icon: {
'x-slot-node': {
target: 'input.x-component-props.addonAfter',
},
'x-component':
'{{$form.values.input?.length > 5 ? "CheckCircleTwoTone" : "CloseCircleOutlined"}}',
},
input: {
type: 'string',
'x-component': 'Input',
'x-component-props': {},
},
},
}}
></SchemaField>
</FormProvider>
)
```

## JSON Schema Render Prop 用例 (x-slot-node & isRenderProp)

参考[Slot](https://react.formilyjs.org/zh-CN/api/shared/schema#slot)

```tsx
import React from 'react'
import { createForm } from '@formily/core'
import { FormProvider, createSchemaField } from '@formily/react'
import { Rate } from 'antd'
import { DollarOutlined } from '@ant-design/icons'

const form = createForm()

const SchemaField = createSchemaField({
components: {
DollarOutlined,
},
})

export default () => (
<FormProvider form={form}>
<SchemaField
components={{
Rate,
}}
schema={{
type: 'object',
properties: {
icon: {
'x-slot-node': {
target: 'rate.x-component-props.character',
isRenderProp: true,
},
'x-component': 'DollarOutlined',
'x-component-props': {
rotate: '{{ $slotArgs[0].value * 45 }}',
style: {
fontSize: '50px',
},
},
},
rate: {
'x-component': 'Rate',
'x-component-props': {
defaultValue: 3,
},
},
},
}}
></SchemaField>
</FormProvider>
)
```
82 changes: 82 additions & 0 deletions packages/react/docs/api/shared/Schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Create a Schema Tree based on a piece of json schema data to ensure that each sc
| $ref | Read the Schema from the Schema predefined and merge it into the current Schema | String | - |
| x-data | Extends Data | Object | `data` |
| x-compile-omitted | list of attributes to ignore compiled expressions | string[] | `[]` |
| x-slot-node | Slot node mark | [Slot](#slot) | - |

#### Detailed description

Expand Down Expand Up @@ -957,6 +958,83 @@ Writing method two, operating the Schema protocol
}
```

### Slot

#### Description

Mark this node as a Slot node, which will be skipped in the normal rendering process.
Use `target` to specify the target property for rendering this node, which must be a sibling property at the same level.
You can use `isRenderProp` to specify that this node is passed in the form of the renderProp function.
When `isRenderProp` is `true`, the renderProp function’s argument list can be accessed within the Slot through `$slotArgs`.

#### Signature

```ts
type Slot = {
//Slot target: Specify the target property for rendering this node, which must be a sibling property at the same level.
target: string // 'some-sibling-node.x-component-props.xxx' or 'some-sibling-node.x-decorator-props.xxx'
//whether it is a renderProp Slot
isRenderProp?: boolean
}
```

#### Example

**ReactNode Prop**
Reference [SchemaField](https://react.formilyjs.org/api/components/schema-field#json-schema-reactnode-prop-use-case-x-slot-node)

```json
{
"type": "object",
"properties": {
"search_icon": {
"x-slot-node": {
"target": "button.x-component-props.icon" //Specify to render the search_icon node as a slot into the icon prop of the Button component.
},
"x-component": "SearchOutlined",
"x-component-props": {
"data-testid": "icon"
}
},
"button": {
"type": "string",
"x-component": "Button",
"x-component-props": {
"data-testid": "button"
}
}
}
}
```

**RenderProp**
Reference [SchemaField](https://react.formilyjs.org/api/components/schema-field#json-schema-render-prop-use-case-x-slot-node--isrenderprop)

```json
{
"type": "object",
"properties": {
"dollar_icon": {
"x-slot-node": {
"target": "rate.x-component-props.character", //Specify to render the dollar_icon node as a slot into the character prop of the Rate component.
"isRenderProp": true //The character prop accepts a renderProp function. Specify the Slot as a renderProp to take control of the rendering of the rating icons.
},
"x-component": "DollarOutlined",
"x-component-props": {
"data-testid": "icon",
"rotate": "{{$slotArgs[0].value * 45}}", //When isRenderProp is true, the renderProp function’s argument list can be accessed within the Slot through $slotArgs.
"style": {
"fontSize": "50px"
}
}
},
"rate": {
"x-component": "Rate"
}
}
}
```

## Built-in expression scope

Built-in expression scope is mainly used to realize various linkage relationships in expressions
Expand Down Expand Up @@ -996,3 +1074,7 @@ It can only be consumed by expressions in x-reactions, corresponding to the depe
### $target

Can only be consumed in expressions in x-reactions, representing the target field of active mode

### $slotArgs

Can only be consumed in slot node. When slot used as render prop, $slotArgs can access render function arguments array
Loading
Loading