From e64d5dcc98fb7e1b72a43a0bb5bdbd9f13272ff2 Mon Sep 17 00:00:00 2001 From: "christian.hausknecht" Date: Tue, 2 Jan 2024 16:49:27 +0100 Subject: [PATCH] Improves PopUpPanel - extends `arrow`-factory to take some `offset`-parameter alongside to the size parameter that is meant for changing width and height - improves default styling, so that the arrow is located behind the panels content - adds, corrects or improves API- and prose-documentation regarding the `PopUpPanel` usage, `arrow`-usage or `middleware`-usage. --- .../fritz2/headlessdemo/components/popOver.kt | 6 + .../fritz2/headless/foundation/PopUpPanel.kt | 167 ++++++++++-------- www/src/pages/headless/headlessComponents.md | 11 +- www/src/pages/headless/listbox.md | 4 +- www/src/pages/headless/menu.md | 4 +- www/src/pages/headless/popOver.md | 9 +- www/src/pages/headless/tooltip.md | 10 +- 7 files changed, 121 insertions(+), 90 deletions(-) diff --git a/headless-demo/src/jsMain/kotlin/dev/fritz2/headlessdemo/components/popOver.kt b/headless-demo/src/jsMain/kotlin/dev/fritz2/headlessdemo/components/popOver.kt index 96bf61101..ea1cd8916 100644 --- a/headless-demo/src/jsMain/kotlin/dev/fritz2/headlessdemo/components/popOver.kt +++ b/headless-demo/src/jsMain/kotlin/dev/fritz2/headlessdemo/components/popOver.kt @@ -4,9 +4,15 @@ package dev.fritz2.headlessdemo.components import dev.fritz2.core.RenderContext import dev.fritz2.core.transition import dev.fritz2.headless.components.popOver +import dev.fritz2.headless.foundation.PopUpPanelSize +import dev.fritz2.headless.foundation.utils.floatingui.core.Middleware +import dev.fritz2.headless.foundation.utils.floatingui.core.MiddlewareReturn +import dev.fritz2.headless.foundation.utils.floatingui.core.MiddlewareState import dev.fritz2.headless.foundation.utils.floatingui.core.middleware.offset import dev.fritz2.headless.foundation.utils.floatingui.utils.PlacementValues import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull +import kotlin.js.Promise fun RenderContext.popOverDemo() { diff --git a/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/PopUpPanel.kt b/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/PopUpPanel.kt index 3a97140ce..7b4c68618 100644 --- a/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/PopUpPanel.kt +++ b/headless/src/jsMain/kotlin/dev/fritz2/headless/foundation/PopUpPanel.kt @@ -71,104 +71,103 @@ abstract class PopUpPanel( addGlobalStyles( listOf( """.$POPUP_RELATIVE { - position: relative; + position: relative; }""".trimIndent(), """.popup[data-popup-reference-hidden] { - visibility: hidden; - pointer-events: none; - }""".trimIndent(), + visibility: hidden; + pointer-events: none; + }""".trimIndent(), """.popup-arrow-default { - width: 8px; - height: 8px; - background: inherit; - }""".trimIndent(), + width: 8px; + height: 8px; + }""".trimIndent(), """.popup-arrow::before { + content: ''; + transform: rotate(45deg); + background: inherit; width: 100%; height: 100%; - }""".trimIndent(), + }""".trimIndent(), """.popup-arrow, .popup-arrow::before { - position: absolute; - }""".trimIndent(), + position: absolute; + z-index: -1; + }""".trimIndent(), """.popup-arrow { - visibility: hidden; - }""".trimIndent(), - """.popup-arrow::before { - content: ''; - transform: rotate(45deg); - background: inherit; - }""".trimIndent(), + visibility: hidden; + background: inherit; + }""".trimIndent(), """.popup-arrow::before, .popup.$FRITZ2_POPUP_VISIBLE .popup-arrow::before { - visibility: visible; - }""".trimIndent(), + visibility: visible; + }""".trimIndent(), """.popup-arrow::before, .popup.$FRITZ2_POPUP_HIDDEN .popup-arrow::before { - visibility: hidden; - }""".trimIndent(), + visibility: hidden; + }""".trimIndent(), """.popup[data-popup-placement^='bottom'] .popup-arrow::before { - top: -50%; - }""".trimIndent(), + top: -50%; + }""".trimIndent(), """.popup[data-popup-placement^='top'] .popup-arrow::before { - bottom: -50%; - }""".trimIndent(), + bottom: -50%; + }""".trimIndent(), """.popup[data-popup-placement^='left'] .popup-arrow::before { - right: -50%; - }""".trimIndent(), + right: -50%; + }""".trimIndent(), """.popup[data-popup-placement^='right'] .popup-arrow::before { - left: -50%; - }""".trimIndent(), + left: -50%; + }""".trimIndent(), """.popup[data-popup-placement^='bottom'] .popup-arrow { - top: 0; - }""".trimIndent(), + top: 0; + }""".trimIndent(), """.popup[data-popup-placement^='top'] .popup-arrow { - bottom: 0; - }""".trimIndent(), + bottom: 0; + }""".trimIndent(), """.popup[data-popup-placement^='left'] .popup-arrow { - right: 0; - }""".trimIndent(), + right: 0; + }""".trimIndent(), """.popup[data-popup-placement^='right'] .popup-arrow { - left: 0; - }""".trimIndent(), + left: 0; + }""".trimIndent(), """.popup[data-popup-placement='bottom'] > .transform { - transform-origin: top; - }""".trimIndent(), + transform-origin: top; + }""".trimIndent(), """.popup[data-popup-placement='bottom-start'] > .transform { - transform-origin: top left; - }""".trimIndent(), + transform-origin: top left; + }""".trimIndent(), """.popup[data-popup-placement='bottom-right'] > .transform { - transform-origin: top right; - }""".trimIndent(), + transform-origin: top right; + }""".trimIndent(), """.popup[data-popup-placement='top'] > .transform { - transform-origin: bottom; - }""".trimIndent(), + transform-origin: bottom; + }""".trimIndent(), """.popup[data-popup-placement='top-start'] > .transform { - transform-origin: bottom left; - }""".trimIndent(), + transform-origin: bottom left; + }""".trimIndent(), """.popup[data-popup-placement='top-right'] > .transform { - transform-origin: bottom right; - }""".trimIndent(), + transform-origin: bottom right; + }""".trimIndent(), """.popup[data-popup-placement='left'] > .transform { - transform-origin: right; - }""".trimIndent(), + transform-origin: right; + }""".trimIndent(), """.popup[data-popup-placement='left-start'] > .transform { - transform-origin: top right; - }""".trimIndent(), + transform-origin: top right; + }""".trimIndent(), """.popup[data-popup-placement='left-end'] > .transform { - transform-origin: bottom right; - }""".trimIndent(), + transform-origin: bottom right; + }""".trimIndent(), """.popup[data-popup-placement='right'] > .transform { - transform-origin: left; - }""".trimIndent(), + transform-origin: left; + }""".trimIndent(), """.popup[data-popup-placement='right-start'] > .transform { - transform-origin: top left; - }""".trimIndent(), + transform-origin: top left; + }""".trimIndent(), """.popup[data-popup-placement='right-end'] > .transform { - transform-origin: bottom left; - }""".trimIndent(), + transform-origin: bottom left; + }""".trimIndent(), """.$FRITZ2_POPUP_VISIBLE { - visibility: visible; - }""".trimIndent(), + visibility: visible; + }""".trimIndent(), """.$FRITZ2_POPUP_HIDDEN { - visibility: hidden; - }""".trimIndent(), + visibility: hidden; + }""".trimIndent(), ) ) } @@ -201,10 +200,17 @@ abstract class PopUpPanel( addMiddleware(flip()) } - /** * Adds a new Middleware to the array of middlewares. * + * For example + * ```kotlin + * addMiddleware(offset(10)) + * ``` + * + * Be aware to follow the recommended precedences by floating-ui's middlewares: + * https://floating-ui.com/docs/middleware#ordering + * * Check https://floating-ui.com/docs/middleware for available middlewares. * * @see ComputePositionConfig.middleware @@ -218,14 +224,29 @@ abstract class PopUpPanel( /** * Adds an arrow to the PopupPanel. The exact position will be calculated by the FloatingUI component and can be * collected from [computedPosition]. The arrow points to the reference element. + * + * By default, a width and height of `8px` each is set by the default class. + * If you want to change this, you must provide both properties somehow, depending on your CSS handling / framework. + * + * The [offset] from the reference element has a default of `5px` and can be also adapted as needed. + * + * Remember that fritz2 is completely CSS framework-agnostic! + * + * @param size the size of the arrow using any valid CSS `width` or `height` expression. Defaults to `8` each + * @param offset the distance between the reference element and the panel in pixels. Defaults to `5` */ - fun arrow(c: String = "popup-arrow-default") { - div(classes(c, "popup-arrow")) { + fun arrow(size: String = "popup-arrow-default", offset: Int = 5) { + div(classes(size, "popup-arrow")) { arrow = this + addMiddleware(offset(offset)) addMiddleware(arrow { element = domNode }) - addMiddleware(offset(5)) inlineStyle(computedPosition.mapNotNull { it.middlewareData?.arrow } - .map { "left: ${it.x}px; top: ${it.y}px;" }) + .map { + buildString { + it.x?.let { x -> append("left: ${x}px;") } + it.y?.let { y -> append(" top: ${y}px;") } + } + }) } } @@ -251,7 +272,9 @@ abstract class PopUpPanel( attr("data-popup-placement", computedPosition.map { it.placement ?: "" }) inlineStyle(computedPosition.map { listOfNotNull( - "position: ${it.strategy}", "left: ${it.x}px", "top: ${it.y}px", + "position: ${it.strategy}", + it.x?.let { x -> "left: ${x}px" }, + it.y?.let { y -> "top: ${y}px" }, when (size) { PopUpPanelSize.Min -> "min-width: ${reference.domNode.offsetWidth}px" PopUpPanelSize.Max -> "max-width: ${reference.domNode.offsetWidth}px" diff --git a/www/src/pages/headless/headlessComponents.md b/www/src/pages/headless/headlessComponents.md index 8ff494778..f7c96a0b3 100644 --- a/www/src/pages/headless/headlessComponents.md +++ b/www/src/pages/headless/headlessComponents.md @@ -507,18 +507,19 @@ in order to influence the positioning of the content: | Scope property | Typ | Description | |----------------|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `size` | `PopUpPanelSize` | Defines the width restrictions of the building block, e.g. `PopUpPanelSize.Min`, `PopUpPanelSize.Max`, etc. | -| `placement` | `Placement` | Defines the position of the building block, e.g. `Placement.top`, `Placement.bottomRight`, etc. | +| `placement` | `PlacementValues` | Defines the position of the building block, e.g. `PlacementValues.top`, `PlacementValues.bottom`, etc. | | `strategy` | `Strategy` | Determines whether the block should be positioned `absolute` (default) or `fixed`. | | `middleware` | `Array` | Middleware are plain objects that modify the positioning coordinates in some fashion, or provide useful data for rendering, as calculated by the positioning cycle. | In addition, an arrow can be added pointing to the reference element. By default, the arrow is 8 pixels wide and -inherits the background color of the panel. It can be styled as usual: +inherits the background color of the panel. You are recommended to only change its `width` or `height` by providing +any valid CSS expression for that for its `size`parameter. Alongside of changing the size, you usually also have to +adapt the `offset` too, so there is also a parameter to provide the value in pixels: ```kotlin -popOverPanel { +popOverPanel("bg-gray-200") { //... - - arrow("h-3 w-3 bg-white") + arrow("h-3 w-3", 8) // w-3 -> 12px in tailwindcss, use at least the half of the arrow size for the offset } ``` diff --git a/www/src/pages/headless/listbox.md b/www/src/pages/headless/listbox.md index d812e3d85..7fcc59a19 100644 --- a/www/src/pages/headless/listbox.md +++ b/www/src/pages/headless/listbox.md @@ -192,8 +192,8 @@ as a reference element: ```kotlin listboxItems { - placement = Placement.Top - distance = 20 + placement = PlacementValues.top + addMiddleware(offset(20)) characters.forEach { (entry, disabledState) -> listboxItem(entry) { diff --git a/www/src/pages/headless/menu.md b/www/src/pages/headless/menu.md index ff31eb012..cf0c3ed52 100644 --- a/www/src/pages/headless/menu.md +++ b/www/src/pages/headless/menu.md @@ -173,8 +173,8 @@ as a reference element: ```kotlin menuItems { - placement = Placement.Top - distance = 20 + placement = PlacementValues.top + addMiddleware(offset(20)) menuItem { //... diff --git a/www/src/pages/headless/popOver.md b/www/src/pages/headless/popOver.md index 04df84864..bab1490c3 100644 --- a/www/src/pages/headless/popOver.md +++ b/www/src/pages/headless/popOver.md @@ -100,8 +100,8 @@ as a reference element: ```kotlin popOverPanel { - placement = Placement.Bottom - distance = 20 + placement = PlacementValues.bottom + addMiddleware(offset(20)) //... } @@ -119,10 +119,11 @@ popOverPanel { } ``` -By default, the arrow is 8 pixels wide and inherits the background-color from the `popOverPanel` but it can easily be styled by adding classes: +By default, the arrow is 8 pixels wide and inherits the background-color from the `popOverPanel` its size and offset can +be adapted: ```kotlin -arrow("h-3 w-3 bg-white") +arrow("h-3 w-3", 8) ``` diff --git a/www/src/pages/headless/tooltip.md b/www/src/pages/headless/tooltip.md index ab345317d..8c4e19f75 100644 --- a/www/src/pages/headless/tooltip.md +++ b/www/src/pages/headless/tooltip.md @@ -74,8 +74,8 @@ button { }.tooltip { + "some description" - placement = Placement.Top - distance = 20 + placement = PlacementValues.top + addMiddleware(offset(20)) } ``` @@ -92,11 +92,11 @@ button { } ``` -By default, the arrow is 8 pixels wide and inherits the background-color from the `tooltip` but it can easily be styled -by added classes: +By default, the arrow is 8 pixels wide and inherits the background-color from the `tooltip` but its size and offset can +be adapted: ```kotlin -arrow("h-3 w-3 bg-white") +arrow("h-3 w-3", 8) ```