Browse Source
* Add Compose for Web tutorials * Update the compose-web tutorials Co-authored-by: Oleksandr Karpovich <oleksandr.karpovich@jetbrains.com>pull/629/head
Oleksandr Karpovich
4 years ago
committed by
GitHub
8 changed files with 522 additions and 0 deletions
@ -0,0 +1,174 @@ |
|||||||
|
# Building the UI with Compose Web |
||||||
|
|
||||||
|
**The API is experimental, and breaking changes can be expected** |
||||||
|
|
||||||
|
## Introduction |
||||||
|
|
||||||
|
In this tutorial we will look at several examples that use the Composable DOM DSL to describe the user interface for your web application. |
||||||
|
|
||||||
|
### Entry point |
||||||
|
|
||||||
|
Compose for Web needs an HTML node that will be a root of its composition. Inside this root node, Compose then manages its own DOM tree. |
||||||
|
|
||||||
|
```kotlin |
||||||
|
renderComposable(rootElementId = "root") { |
||||||
|
// content goes here |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### HTML tags in Compose DOM DSL |
||||||
|
|
||||||
|
While the DOM DSL for Compose for Web doesn't provide a Composable for every HTML tag yet, the most used HTML tags can be used directly out of a box. |
||||||
|
|
||||||
|
Let's have a look at the Composable for a `Div` tag (most other tags have the same signature): |
||||||
|
|
||||||
|
```kotlin |
||||||
|
Div( |
||||||
|
attrs = { |
||||||
|
// specify attributes here |
||||||
|
}, |
||||||
|
style = { |
||||||
|
// specify inline style here |
||||||
|
} |
||||||
|
) { |
||||||
|
// div content goes here |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
For convenience, some tags like `Input`, `A`, `Form`, or `Img` allow you to specify some extra parameters in the signature that are specific to the respective HTML tag. For example, let’s look at the `Input` tag: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
Input( |
||||||
|
type = InputType.Text, // All InputTypes supported |
||||||
|
value = "", // sets the input value |
||||||
|
attrs = {}, |
||||||
|
style = {} |
||||||
|
) |
||||||
|
``` |
||||||
|
|
||||||
|
We can use the `type` parameter which is provided for our convenience, or can use the `attrs` block to specify the input type: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
Input(attrs = { type(InputType.Text) }) |
||||||
|
``` |
||||||
|
|
||||||
|
### Text |
||||||
|
|
||||||
|
The `Text` allows you to add text content to an HTML tag. Besides, the text content it represents, it does not have any parameters: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
Text("Arbitrary text") |
||||||
|
``` |
||||||
|
|
||||||
|
If you want to apply styles to text, it needs to be wrapped in a container with a style applied, like a `Span` or `P`: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
Span( |
||||||
|
style = { color("red") } // inline style |
||||||
|
) { |
||||||
|
Text("Red text") |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
This corresponds to the following HTML code: |
||||||
|
```html |
||||||
|
<span style="color: red;">Red text</span> |
||||||
|
``` |
||||||
|
|
||||||
|
### Attributes |
||||||
|
|
||||||
|
The `attrs` parameter (which we’ve already seen in some of the previous examples) allows us to specify element's attributes and properties. |
||||||
|
|
||||||
|
The most flexible way to define attributes is by using the `attr` function, which allows you to specify the attribute name and its value. |
||||||
|
|
||||||
|
```kotlin |
||||||
|
Div( |
||||||
|
attrs = { |
||||||
|
attr(attr = "custom_attr", value = "its_value") |
||||||
|
} |
||||||
|
) { /* content */ } |
||||||
|
``` |
||||||
|
|
||||||
|
However, with this approach, Compose for Web is not able to validate that the attribute exists on the HTML element, or is valid. This is why we also provide a set of helper functions for common attributes. |
||||||
|
|
||||||
|
#### Common attributes |
||||||
|
|
||||||
|
Here are some examples of common attributes that are available for most Composables representing HTML tags: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
attrs = { |
||||||
|
id("elementId") |
||||||
|
classes("cl1", "cl2") |
||||||
|
hidden(false) |
||||||
|
title("title") |
||||||
|
draggable(Draggable.Auto) |
||||||
|
dir(DirType.Auto) |
||||||
|
lang("en") |
||||||
|
contentEditable(true) |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### Element specific attributes |
||||||
|
|
||||||
|
Depending on the element you are working with, you may also have access to some specific attributes – attributes that are only meaningful for this particular tag. For example, the `A` tag provides some specific attributes, that are specific to hyperlinks: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
A( |
||||||
|
attrs = { |
||||||
|
href("https://localhost:8080/page2") |
||||||
|
target(ATarget.Blank) |
||||||
|
rel(ARel.Next) |
||||||
|
hreflang("en") |
||||||
|
download("https://...") |
||||||
|
} |
||||||
|
) {} |
||||||
|
``` |
||||||
|
|
||||||
|
Some other elements that provide specific attributes include: |
||||||
|
- Button |
||||||
|
- Form |
||||||
|
- Input |
||||||
|
- Option |
||||||
|
- Select |
||||||
|
- OptGroup |
||||||
|
- TextArea |
||||||
|
- Img |
||||||
|
|
||||||
|
To discover all attributes that are available in your current scope, you can use your IDE’s autocomplete feature. As we evolve these APIs, we also plan to add detailed documentation for them. |
||||||
|
|
||||||
|
#### Events |
||||||
|
|
||||||
|
You can declare event listeners in the `attrs` block: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
Button( |
||||||
|
attrs = { |
||||||
|
onClick { println("Button clicked") } |
||||||
|
} |
||||||
|
) { Text("Button") } |
||||||
|
``` |
||||||
|
|
||||||
|
There are more examples about events handling here - [Events Handling](../Events_Handling/README.md) |
||||||
|
|
||||||
|
### Style |
||||||
|
|
||||||
|
There are ways to set the style for a component: |
||||||
|
- Using inline styles |
||||||
|
- Using stylesheets |
||||||
|
|
||||||
|
You can declare inline styles via the `style` block of a component: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
|
||||||
|
Div( |
||||||
|
style = { |
||||||
|
display(DisplayStyle.Flex) |
||||||
|
padding(20.px) |
||||||
|
|
||||||
|
// custom property |
||||||
|
property("font-family", value("Arial, Helvetica, sans-serif")) |
||||||
|
} |
||||||
|
) { /* content goes here */ } |
||||||
|
``` |
||||||
|
|
||||||
|
You can find a more detailed overview of the style DSL, as well as additional examples here - [Style DSL](../Style_Dsl/README.md) |
@ -0,0 +1,42 @@ |
|||||||
|
# Events handling in Compose Web |
||||||
|
|
||||||
|
**The API is experimental, and breaking changes can be expected** |
||||||
|
|
||||||
|
You can add event listeners in the `attrs` block: |
||||||
|
|
||||||
|
#### onClick |
||||||
|
```kotlin |
||||||
|
Button( |
||||||
|
attrs = { |
||||||
|
onClick { wrappedMouseEvent -> |
||||||
|
// wrappedMouseEvent is of `WrappedMouseEvent` type |
||||||
|
println("button clicked at ${wrappedMouseEvent.movementX}, ${wrappedMouseEvent.movementY}") |
||||||
|
|
||||||
|
val nativeEvent = wrappedMouseEvent.nativeEvent // [MouseEvent](https://developer.mozilla.org/en/docs/Web/API/MouseEvent) |
||||||
|
} |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("Button") |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### onInput |
||||||
|
```kotlin |
||||||
|
val text = remember { mutableStateOf("") } |
||||||
|
|
||||||
|
TextArea( |
||||||
|
value = text.value, |
||||||
|
attrs = { |
||||||
|
onTextInput { wrappedTextInputEvent -> |
||||||
|
// wrappedTextInputEvent is of `WrappedTextInputEvent` type |
||||||
|
text.value = wrappedTextInputEvent.inputValue |
||||||
|
} |
||||||
|
} |
||||||
|
) |
||||||
|
``` |
||||||
|
|
||||||
|
Your event handlers receive wrapped events that inherit from `GenericWrappedEvent`, which also provides access to the underlying `nativeEvent` – the actual event created by JS runtime - |
||||||
|
https://developer.mozilla.org/en-US/docs/Web/API/Event |
||||||
|
|
||||||
|
|
||||||
|
There are more event listeners supported out of a box. We plan to add the documentation for them later on. In the meantime, you can find all supported event listeners in the [source code](https://github.com/JetBrains/androidx/blob/compose-web-main/compose/web/src/jsMain/kotlin/androidx/compose/web/attributes/EventsListenerBuilder.kt). |
@ -0,0 +1,133 @@ |
|||||||
|
# Getting Started With Compose for Web |
||||||
|
|
||||||
|
**The API is experimental, and breaking changes can be expected** |
||||||
|
|
||||||
|
## Introduction |
||||||
|
|
||||||
|
In this tutorial, we will create a simple web UI application using the Compose UI framework. |
||||||
|
|
||||||
|
|
||||||
|
## Prerequisites |
||||||
|
|
||||||
|
You need to have the following software installed before you begin: |
||||||
|
* JDK 11 or later |
||||||
|
* IntelliJ IDEA Community Edition or Ultimate Edition 2020.2 or later (you can use other editors, but for this tutorial we assume you are using IntelliJ IDEA) |
||||||
|
|
||||||
|
|
||||||
|
## Creating a new project |
||||||
|
|
||||||
|
If you don't want to create the project manually, you can [download the example here](https://github.com/JetBrains/compose-jb/tree/master/examples/web-getting-started) |
||||||
|
|
||||||
|
The project wizard doesn't support Compose for web projects yet, so we need to perform the following steps: |
||||||
|
|
||||||
|
#### 1. Create a Kotlin Multiplatform project: |
||||||
|
- Select `Gradle` on the left menu |
||||||
|
- Tick `Kotlin DSL build script` |
||||||
|
- Tick `Kotlin/Multiplatform` |
||||||
|
|
||||||
|
![](create-mpp.png) |
||||||
|
|
||||||
|
|
||||||
|
#### 2. Update `settings.gradle.kts`: |
||||||
|
``` kotlin |
||||||
|
pluginManagement { |
||||||
|
repositories { |
||||||
|
gradlePluginPortal() |
||||||
|
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### 3. Update `build.gradle.kts`: |
||||||
|
```kotlin |
||||||
|
// Add compose gradle plugin |
||||||
|
plugins { |
||||||
|
kotlin("multiplatform") version "1.4.32" |
||||||
|
id("org.jetbrains.compose") version "0.0.0-web-dev-5" |
||||||
|
} |
||||||
|
|
||||||
|
// Add maven repositories |
||||||
|
repositories { |
||||||
|
mavenCentral() |
||||||
|
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") |
||||||
|
} |
||||||
|
|
||||||
|
// Enable JS(IR) target and add dependencies |
||||||
|
kotlin { |
||||||
|
js(IR) { |
||||||
|
browser() |
||||||
|
binaries.executable() |
||||||
|
} |
||||||
|
sourceSets { |
||||||
|
val jsMain by getting { |
||||||
|
dependencies { |
||||||
|
implementation(compose.web.web) |
||||||
|
implementation(compose.runtime) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### 5. Add the following directories to the project: |
||||||
|
- src/jsMain/kotlin |
||||||
|
- src/jsMain/resources |
||||||
|
|
||||||
|
#### 6. Add the `index.html` file to the `resources`: |
||||||
|
```html |
||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<title>Sample</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="root"></div> |
||||||
|
<script src="REPLACE_WITH_YOUR_MODULE_NAME.js"></script> |
||||||
|
</body> |
||||||
|
</html> |
||||||
|
``` |
||||||
|
|
||||||
|
#### 7. Add the `Main.kt` file: |
||||||
|
```kotlin |
||||||
|
fun main() { |
||||||
|
var count: Int by mutableStateOf(0) |
||||||
|
|
||||||
|
renderComposable(rootElementId = "root") { |
||||||
|
Div(style = { padding(25.px) }) { |
||||||
|
Button(attrs = { |
||||||
|
onClick { count = count - 1 } |
||||||
|
}) { |
||||||
|
Text("-") |
||||||
|
} |
||||||
|
|
||||||
|
Span(style = { padding(15.px) }) { |
||||||
|
Text("${count}") |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
Button(attrs = { |
||||||
|
onClick { count = count + 1 } |
||||||
|
}) { |
||||||
|
Text("+") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
## Running the project |
||||||
|
|
||||||
|
Use the command line to run: |
||||||
|
|
||||||
|
```shell |
||||||
|
./gradlew jsBrowserRun |
||||||
|
``` |
||||||
|
|
||||||
|
Or run it from the IDE: |
||||||
|
|
||||||
|
![](run_project.png) |
||||||
|
|
||||||
|
The browser will open `localhost:8080`: |
||||||
|
|
||||||
|
![](run_result.png) |
After Width: | Height: | Size: 233 KiB |
After Width: | Height: | Size: 620 KiB |
After Width: | Height: | Size: 66 KiB |
@ -0,0 +1,12 @@ |
|||||||
|
# Compose for Web |
||||||
|
**The API is experimental, and breaking changes can be expected** |
||||||
|
|
||||||
|
### Content: |
||||||
|
|
||||||
|
[Getting Started With Compose for Web](Getting_Started/README.md) - create and configure a simple project |
||||||
|
|
||||||
|
[Building web UI](Building_UI/README.md) - overview of the basic features Compose for Web provides to build web UIs |
||||||
|
|
||||||
|
[Handing Events](Events_Handling/README.md) - a short overview of Events handling with compose web |
||||||
|
|
||||||
|
[Style DSL](Style_Dsl/README.md) - about styling the composable components in web |
@ -0,0 +1,161 @@ |
|||||||
|
# Style DSL in Compose Web |
||||||
|
**The API is experimental, and breaking changes can be expected** |
||||||
|
|
||||||
|
## Introduction |
||||||
|
In this tutorial we have a look at how to style the components using the Style DSL. It’s a typesafe DSL for style sheets, which you can use to express CSS rules in your Kotlin code, and even modify styles based on the state of your Compose application. |
||||||
|
|
||||||
|
|
||||||
|
### Inline Style |
||||||
|
|
||||||
|
You can declare inline styles via the `style` block of a component |
||||||
|
|
||||||
|
```kotlin |
||||||
|
Div( |
||||||
|
style = { |
||||||
|
display(DisplayStyle.Flex) |
||||||
|
padding(20.px) |
||||||
|
|
||||||
|
// custom property (or not supported out of a box) |
||||||
|
property("font-family", value("Arial, Helvetica, sans-serif")) |
||||||
|
} |
||||||
|
) { /* content goes here */ } |
||||||
|
``` |
||||||
|
|
||||||
|
In HTML, it will look like this: |
||||||
|
|
||||||
|
```html |
||||||
|
<div style="display: flex; padding: 20px; font-family: Arial, Helvetica, sans-serif;"></div> |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
### Stylesheet |
||||||
|
An alternative way is to define a Stylesheet that contains rules: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
object AppStylesheet : StyleSheet() { |
||||||
|
val container by style { // container is a class |
||||||
|
display(DisplayStyle.Flex) |
||||||
|
padding(20.px) |
||||||
|
|
||||||
|
// custom property (or not supported out of a box) |
||||||
|
property("font-family", value("Arial, Helvetica, sans-serif")) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Stylesheet needs to be mounted: |
||||||
|
renderComposable("root") { |
||||||
|
Style(AppStylesheet) |
||||||
|
|
||||||
|
Container { |
||||||
|
Text("Content") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun Container(content: @Composable () -> Unit) { |
||||||
|
Div( |
||||||
|
attrs = { classes(AppStylesheet.container) } |
||||||
|
) { |
||||||
|
content() |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
In HTML, it will look like this: |
||||||
|
|
||||||
|
```html |
||||||
|
<style></style> |
||||||
|
<div class="AppStylesheet-container">Content</div> |
||||||
|
``` |
||||||
|
|
||||||
|
### Selectors examples |
||||||
|
|
||||||
|
The Style DSL also provides a way to combine and unify selectors: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
object AppStylesheet : StyleSheet() { |
||||||
|
|
||||||
|
init { |
||||||
|
// CSSSelector.Universal can be used instead of "*" |
||||||
|
"*" style { |
||||||
|
fontSize(15.px) |
||||||
|
padding(0.px) |
||||||
|
} |
||||||
|
|
||||||
|
// raw selector |
||||||
|
"h1, h2, h3, h4, h5, h6" style { |
||||||
|
property("font-family", value("Arial, Helvetica, sans-serif")) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// combined selector |
||||||
|
type("A") + attr( // selects all tags <a> with href containing 'jetbrains' |
||||||
|
name = "href", |
||||||
|
value = "jetbrains", |
||||||
|
operator = CSSSelector.Attribute.Operator.Equals |
||||||
|
) style { |
||||||
|
fontSize(25.px) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// A convenient way to create a class selector |
||||||
|
// AppStylesheet.container can be used a class name in components |
||||||
|
val container by style { |
||||||
|
color("red") |
||||||
|
|
||||||
|
// hover selector for a class |
||||||
|
self + hover() style { // self is a selector for `container` |
||||||
|
color("green") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
|
||||||
|
### Media query example |
||||||
|
|
||||||
|
To specify media queries, you can use the `media` function, which takes the related query, and a block of styles: |
||||||
|
|
||||||
|
```kotlin |
||||||
|
object AppStylesheet : StyleSheet() { |
||||||
|
val container by style { |
||||||
|
padding(48.px) |
||||||
|
|
||||||
|
media(maxWidth(640.px)) { |
||||||
|
self style { |
||||||
|
padding(12.px) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### CSS Variables |
||||||
|
|
||||||
|
The style DSL also provides support for CSS variables. |
||||||
|
|
||||||
|
```kotlin |
||||||
|
object MyVariables : CSSVariables { |
||||||
|
// declare a variable |
||||||
|
val contentBackgroundColor by variable<Color>() |
||||||
|
} |
||||||
|
|
||||||
|
object MyStyleSheet: StyleSheet() { |
||||||
|
|
||||||
|
val container by style { |
||||||
|
//set variable's value for the `container` scope |
||||||
|
MyVariables.contentBackgroundColor(Color("blue")) |
||||||
|
} |
||||||
|
|
||||||
|
val content by style { |
||||||
|
// get the value |
||||||
|
backgroundColor(MyVariables.contentBackgroundColor.value()) |
||||||
|
} |
||||||
|
|
||||||
|
val contentWithDefaultBgColor by style { |
||||||
|
// default value can be provided as well |
||||||
|
// default value is used when the value is not previously set |
||||||
|
backgroundColor(MyVariables.contentBackgroundColor.value(Color("#333"))) |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
Loading…
Reference in new issue