Skip to content

Commit

Permalink
Closes #844: Add documentation for component replacement
Browse files Browse the repository at this point in the history
Explain what is in the updated ComponentReplacementContext class and how to use with non-DesignCompose replacement composables.
  • Loading branch information
rylin8 committed Mar 19, 2024
1 parent faa97d9 commit d34aad8
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,6 @@ interface ComponentReplacementContext {
// retain the original node's layout (size, position) properties.
val layoutModifier: Modifier

// Render the content of this replaced component, if any.
@Composable fun Content(): Unit

// Return the text style, if the component being replaced is a text node in the Figma
// document.
val textStyle: TextStyle?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,6 @@ internal fun DesignFrame(
object : ComponentReplacementContext {
override val layoutModifier = Modifier.layoutStyle(name, layoutId)
override val textStyle: TextStyle? = null

@Composable
override fun Content() {
content()
}
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,6 @@ internal fun DesignText(
object : ComponentReplacementContext {
override val layoutModifier = textModifier
override val textStyle = textStyle

@Composable override fun Content() {}
}
)
} else {
Expand Down
85 changes: 58 additions & 27 deletions docs/_docs/modifiers/modifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,56 +256,86 @@ VariantTestDoc.Square(

### Component replacement {#comp-replace}

With a [`Composable`][2] of your choice, an element of a design can be replaced
while retaining the style information provided by the designer. The difference
between replacing versus adding descendants (as shown in [Creating lists][5] is
that a replaced Figma design element isn't rendered.
An node in a design can be replaced with a [`Composable`][2] of your choice while
retaining the style information provided by the designer. The difference between replacing
versus adding descendants (as shown in [Creating lists][5] is that a replaced Figma design
element isn't rendered.

The example below describes how to replace a component:
The example below describes a simple way to replace a component. The `placeholderButton` parameter
takes the type `@Composable (ComponentReplacementContext) -> Unit`, which means that while
traversing the UI tree, it will replace any node named `#placeholder-button` with the composable
specified by the parameter. In this example, the `placeholderButton` is replaced by the node
`#replacement-button` by calling the generated function `HelloWorldDoc.ReplacementButton()`.

```kotlin
@DesignDoc(id = "<your figma doc id>")
interface HelloWorld {
@DesignComponent(node = "#MainFrame")
fun MainFrame(
@Design(node = "#button") button: @Composable (ComponentReplacementContext) -> Unit,
@Design(node = "#placeholder-button") placeholderButton: @Composable (ComponentReplacementContext) -> Unit,
)
@DesignComponent(node = "#replacement-button")
fun ReplacementButton()
}
```

The example below passes a `Composable` `fun` that accepts
`software.vehicle.design.ComponentReplacementContext`. This object provides
methods to access the style of the replaced element and its descendants.
Component Replacements are provided with information on the component they are
replacing including the modifiers that would be applied for the layout style,
visual style, and (if applicable) the text style.
@Composable
fun Main() {
HelloWorldDoc.MainFrame(
button = {
HelloWorldDoc.ReplacementButton()
}
)
}
```

Doing so allows a replacement component to take on the designer specified
The above example ignores the `ComponentReplacementContext` parameter, as it is often not needed.
However, it can be used to obtain extra data about the node being replaced.
The `ComponentReplacementContext` object provides
a field to access the layout properties of the replaced element, and another field
to access the text style if the replaced element is a text node. Doing so allows a
replacement component to take on the designer specified size, position, and text
appearance while offering more behavioral changes than are permitted with simple
Modifier customizations. For example, replacing a styled text node with a
complete text field.

```kotlin
interface ComponentReplacementContext {
/// Return the layout modifier that would have been used to present this component.
/// This modifier doesn't contain any appearance information, and should be the first
/// in the modifier chain.
// Return the custom layout modifier that this component would have used so that the layout
// function can retrieve the component's layout properties. When replacing a node with a
// composable that is not a DesignCompose generated function, such as a simple Box() or an
// AndroidView, this modifier should be used as a modifier for that component in order for it to
// retain the original node's layout (size, position) properties.
val layoutModifier: Modifier

/// Return the appearance modifier. This causes the view to be presented as specified
/// by the designer. It also includes any modifier customizations that may have been
/// specified elsewhere in the program.
val appearanceModifier: Modifier

/// Render the content of this replaced component, if any.
@Composable fun Content(): Unit

/// Return the text style, if the component being replaced is a text node in the Figma
/// document.
// Return the text style, if the component being replaced is a text node in the Figma
// document.
val textStyle: TextStyle?
}
```

As shown in the first example, when replacing a node with another DesignCompose generated composable,
the `layoutModifier` from the `ComponentReplacementContext` can be ignored. DesignCompose will
automatically use the original node's layout properties, but compose the replacement node instead.
When replacing a node with a composable that was not generated by DesignCompose, that composable
does not call into any DesignCompose code, and thus has no knowledge of the original node's layout.
In order to use the original node's size and position, the replacement composable should use the
`layoutModifier` from `ComponentReplacementContext`. The example below describes how to do this
with a `Box` as the replacement composable:

```kotlin
HelloWorldDoc.MainFrame(
button = { context: ComponentReplacementContext ->
Box(
Modifier.background(Color.Green) // draw the box green
.then(context.layoutModifier) // take the size and position of the original node
)
}
)
```

The next section on [text input](#text-input) explains how to use the `textStyle` field to retain
the designer's style for text input.

## Text input {#text-input}

This section addresses how to add text input fields to an app.
Expand All @@ -326,6 +356,7 @@ interface HelloWorld {
val (text, setText) = remember { mutableStateOf("editable") }
HelloWorldDoc.MainFrame(Modifier.fillMaxSize()) { context ->
BasicTextField(
modifier = context.layoutModifier,
value = text,
onValueChange = setText,
textStyle = context.textStyle ?: TextStyle.Default
Expand Down

0 comments on commit d34aad8

Please sign in to comment.