diff --git a/demo/index.html b/demo/index.html index 568a5f8..93dc802 100644 --- a/demo/index.html +++ b/demo/index.html @@ -9,6 +9,10 @@ /> + =12.0.0" + } + }, "node_modules/html-minifier-terser": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", @@ -1601,6 +1610,11 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "highlight.js": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", + "integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==" + }, "html-minifier-terser": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", diff --git a/demo/package.json b/demo/package.json index fce3843..96d3f97 100644 --- a/demo/package.json +++ b/demo/package.json @@ -8,13 +8,14 @@ "preview": "vite preview" }, "devDependencies": { + "http-proxy": "1.18.1", "vite": "2.9.0", - "vite-plugin-html": "2.0.7", - "http-proxy": "1.18.1" + "vite-plugin-html": "2.0.7" }, "dependencies": { "@ui5/webcomponents": "1.3.0", "@ui5/webcomponents-fiori": "1.3.0", - "@ui5/webcomponents-icons": "1.3.0" + "@ui5/webcomponents-icons": "1.3.0", + "highlight.js": "11.6.0" } } diff --git a/demo/src/main/scala/demo/AvatarExample.scala b/demo/src/main/scala/demo/AvatarExample.scala index 8ddaaef..2f003ef 100644 --- a/demo/src/main/scala/demo/AvatarExample.scala +++ b/demo/src/main/scala/demo/AvatarExample.scala @@ -3,7 +3,7 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} import demo.helpers.MTG object AvatarExample extends Example("Avatar") { @@ -11,25 +11,30 @@ object AvatarExample extends Example("Avatar") { private def sherpal = img(src := "/images/avatars/sherpal.png", alt := "sherpal") private def manaSymbolImage(name: String) = img(src := MTG.manaSymbolsRefs(name), alt := name) - def component: HtmlElement = div( - DemoPanel( - "Basic examples", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic examples") { + //-- Begin: Basic examples div(Avatar(_ => sherpal), Avatar(_.shape := AvatarShape.Square, _ => sherpal)) - ), - DemoPanel( - "Avatar sizes", + //-- End + }, + DemoPanel("Avatar sizes") { + //-- Begin: Avatar sizes div( AvatarSize.allValues .zip(MTG.manaSymbolsShortNames) .map((size, mana) => Avatar(_.size := size, _ => manaSymbolImage(mana))) ) - ), - DemoPanel( - "Avatar with icons", + //-- End + }, + DemoPanel("Avatar with icons") { + //-- Begin: Avatar with icons div(AvatarSize.allValues.zip(IconName.allValues).map((size, icon) => Avatar(_.size := size, _.icon := icon))) - ), - DemoPanel( - "Avatar with initials", + //-- End + }, + DemoPanel("Avatar with initials") { + //-- Begin: Avatar with initials div(AvatarSize.allValues.map { size => val initials: AvatarInitials = size.value.toList.take(2) match { case c :: Nil => c @@ -38,7 +43,8 @@ object AvatarExample extends Example("Avatar") { } Avatar(_.size := size, _.initials := initials) }) - ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/AvatarGroupExample.scala b/demo/src/main/scala/demo/AvatarGroupExample.scala new file mode 100644 index 0000000..990e96b --- /dev/null +++ b/demo/src/main/scala/demo/AvatarGroupExample.scala @@ -0,0 +1,52 @@ +package demo + +import be.doeraene.webcomponents.ui5.* +import be.doeraene.webcomponents.ui5.configkeys.* +import com.raquo.laminar.api.L.* +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} +import org.scalajs.dom + +object AvatarGroupExample extends Example("AvatarGroup") { + + def component(using demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo): HtmlElement = div( + DemoPanel("Avatar Group - type 'Individual'") { + //-- Begin: Avatar Group - type 'Individual' + AvatarGroup( + _.tpe := AvatarGroupType.Individual, + _ => IconName.allValues.take(5).map(icon => Avatar(_.icon := icon, _.size := AvatarSize.M)) + ) + //-- End + }, + DemoPanel("Avatar Group - type 'Group'") { + //-- Begin: Avatar Group - type 'Group' + AvatarGroup( + _.tpe := AvatarGroupType.Group, + _ => IconName.allValues.take(5).map(icon => Avatar(_.icon := icon, _.size := AvatarSize.M)) + ) + //-- End + }, + DemoPanel("Avatar Group with overflow") { + //-- Begin: Avatar Group with overflow + def allAvatars = IconName.allValues.take(10).map(icon => Avatar(_.icon := icon, _.size := AvatarSize.M)) + + val avatarsForPopoverBus: EventBus[List[HtmlElement]] = new EventBus + val popoverOpenerBus: EventBus[dom.HTMLElement] = new EventBus + + div( + AvatarGroup( + _.tpe := AvatarGroupType.Individual, + _ => allAvatars, + _ => width := "400px", + _.events.onClick.map(_.target.hiddenItems.length).map(allAvatars.takeRight) --> avatarsForPopoverBus.writer, + _.events.onClick.filter(_.detail.overflowButtonClicked).map(_.detail.targetRef) --> popoverOpenerBus.writer + ), + Popover( + _.showAtFromEvents(popoverOpenerBus.events), + _ => children <-- avatarsForPopoverBus + ) + ) + //-- End + } + ) + +} diff --git a/demo/src/main/scala/demo/BadgeExample.scala b/demo/src/main/scala/demo/BadgeExample.scala index 514e3cf..cdd6a2c 100644 --- a/demo/src/main/scala/demo/BadgeExample.scala +++ b/demo/src/main/scala/demo/BadgeExample.scala @@ -3,7 +3,7 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object BadgeExample extends Example("Badge") { @@ -20,22 +20,26 @@ object BadgeExample extends Example("Badge") { "the last" ) - def component: HtmlElement = div( - DemoPanel( - "Basic badge", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic badge")( + //-- Begin: Basic badge div( ColourScheme.allValues .zip(someTexts) .map((colourScheme, text) => Badge(_.colourScheme := colourScheme, _ => text)), Badge(_ => width := "200px", _ => "This text is very long and it will be truncated with ellipsis") ) + //-- End ), - DemoPanel( - "Badge with icon", + DemoPanel("Badge with icon")( + //-- Begin: Badge with icon div( Badge(_.colourScheme := ColourScheme._1, _.slots.icon := Icon(_.name := IconName.add), _ => "Add"), Badge(_.colourScheme := ColourScheme._2, _.slots.icon := Icon(_.name := IconName.customer)) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/BarExample.scala b/demo/src/main/scala/demo/BarExample.scala index d3fe7dc..c6f9b77 100644 --- a/demo/src/main/scala/demo/BarExample.scala +++ b/demo/src/main/scala/demo/BarExample.scala @@ -3,7 +3,7 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object BarExample extends Example("Bar") { @@ -23,16 +23,16 @@ object BarExample extends Example("Bar") { _.slots.endContent := Button(_.design := ButtonDesign.Transparent, _ => "Cancel") ) - def component: HtmlElement = div( + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( List(BarDesign.Header, BarDesign.Subheader).map(design => - DemoPanel( - s"${design.value} Bar", + DemoPanel(s"${design.value} Bar")( Bar((headerBarContent(s"${design.value} Title") :+ (_.design := design)): _*) ) ), List(BarDesign.Footer, BarDesign.FloatingFooter).map(design => - DemoPanel( - s"${design.value} Bar", + DemoPanel(s"${design.value} Bar")( Bar(footerBarContent: _*) ) ) diff --git a/demo/src/main/scala/demo/BarcodeScannerDialogExample.scala b/demo/src/main/scala/demo/BarcodeScannerDialogExample.scala new file mode 100644 index 0000000..e71052e --- /dev/null +++ b/demo/src/main/scala/demo/BarcodeScannerDialogExample.scala @@ -0,0 +1,49 @@ +package demo + +import be.doeraene.webcomponents.ui5.* +import be.doeraene.webcomponents.ui5.configkeys.* +import com.raquo.laminar.api.L.* +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} + +object BarcodeScannerDialogExample extends Example("BarcodeScannerDialog") { + + def component(using demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo): HtmlElement = div( + DemoPanel("Usage") { + //-- Begin: Usage + // Feed this bus in order to open the barcode scanner + val openScannerBus: EventBus[Unit] = new EventBus + + // Feed this bus to pass the information of a failed scan + val barcodeScanFailedBus: EventBus[BarcodeScannerDialog.events.ErrorInfo] = new EventBus + // Feed this bus to pass the information of a successful scan + val barcodeScanSuccessBus: EventBus[BarcodeScannerDialog.events.SuccessInfo] = new EventBus + + val barcodeScanEvents = EventStream + .merge( + barcodeScanSuccessBus.events, + barcodeScanFailedBus.events + ) + .mapTo(()) + + div( + BarcodeScannerDialog( + _.showOnEvents(openScannerBus.events), + _.closeOnEvents(barcodeScanEvents), + _.events.onScanSuccess.map(_.detail) --> barcodeScanSuccessBus.writer, + _.events.onScanError.map(_.detail) --> barcodeScanFailedBus.writer + ), + Title.h3(_ => "Click on the button below and show a QR code to the camera:"), + Button( + _.icon := IconName.camera, + _.tooltip := "Start Camera", + _ => "Scan", + _.events.onClick.mapTo(()) --> openScannerBus.writer + ), + div(Label(_ => child.text <-- barcodeScanSuccessBus.events.map(_.text))), + div(Label(_ => child.text <-- barcodeScanFailedBus.events.map(_.message))) + ) + //-- End + } + ) + +} diff --git a/demo/src/main/scala/demo/BreadcrumbsExample.scala b/demo/src/main/scala/demo/BreadcrumbsExample.scala index bf0c71b..8d26eb0 100644 --- a/demo/src/main/scala/demo/BreadcrumbsExample.scala +++ b/demo/src/main/scala/demo/BreadcrumbsExample.scala @@ -3,12 +3,14 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object BreadcrumbsExample extends Example("Breadcrumbs") { - def component: HtmlElement = div( - DemoPanel( - "Standard Breadcrumbs", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Standard Breadcrumbs")( + //-- Begin: Standard Breadcrumbs Breadcrumbs( _.Item( _.href := "https://github.com/sherpal/LaminarSAPUI5Bindings", @@ -19,9 +21,10 @@ object BreadcrumbsExample extends Example("Breadcrumbs") { _.Item(_ => "Current page"), _.events.onItemClick.map(_.detail.item) --> Observer(x => org.scalajs.dom.console.log(x)) ) + //-- End ), - DemoPanel( - "Breadcrumbs with no current page", + DemoPanel("Breadcrumbs with no current page")( + //-- Begin: Breadcrumbs with no current page Breadcrumbs( _.design := BreadcrumbsDesign.NoCurrentPage, _.Item( @@ -31,9 +34,10 @@ object BreadcrumbsExample extends Example("Breadcrumbs") { ), _.Item(_.href := "https://github.com/sherpal/LaminarSAPUI5Bindings", _ => "Parent page") ) + //-- End ), - DemoPanel( - "Breadcrumbs with specific separator", + DemoPanel("Breadcrumbs with specific separator")( + //-- Begin: Breadcrumbs with specific separator div( BreadcrumbsSeparatorStyle.allValues.map(separatorStyle => Breadcrumbs( @@ -48,6 +52,7 @@ object BreadcrumbsExample extends Example("Breadcrumbs") { ) ) ) + //-- End ) ) } diff --git a/demo/src/main/scala/demo/BusyIndicatorExample.scala b/demo/src/main/scala/demo/BusyIndicatorExample.scala index d74795e..6a56704 100644 --- a/demo/src/main/scala/demo/BusyIndicatorExample.scala +++ b/demo/src/main/scala/demo/BusyIndicatorExample.scala @@ -3,13 +3,15 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object BusyIndicatorExample extends Example("BusyIndicator") { - def component: HtmlElement = div( - DemoPanel( - "Busy indicator with difference size", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Busy indicator with difference size")( + //-- Begin: Busy indicator with difference size div( display := "flex", flexDirection := "column", @@ -21,36 +23,37 @@ object BusyIndicatorExample extends Example("BusyIndicator") { ) ) ) + //-- End ), - DemoPanel( - "Busy indicator wrapping other elements", { - val fetchListDataBus: EventBus[Unit] = new EventBus - val allData = (1 to 10).map(j => s"Data $j").toList - val numberOfDataToFetch = fetchListDataBus.events.delay(3000).foldLeft(0)((acc, _) => acc + 3) - val fetchedData = numberOfDataToFetch.map(allData.take) - val busyStates = EventStream - .merge( - fetchListDataBus.events.map(_ => 1), - numberOfDataToFetch.changes.map(_ => -1) - ) - .foldLeft(0)(_ + _) - .map(_ > 0) + DemoPanel("Busy indicator wrapping other elements") { + //-- Begin: Busy indicator wrapping other elements + val fetchListDataBus: EventBus[Unit] = new EventBus + val allData = (1 to 10).map(j => s"Data $j").toList + val numberOfDataToFetch = fetchListDataBus.events.delay(3000).foldLeft(0)((acc, _) => acc + 3) + val fetchedData = numberOfDataToFetch.map(allData.take) + val busyStates = EventStream + .merge( + fetchListDataBus.events.map(_ => 1), + numberOfDataToFetch.changes.map(_ => -1) + ) + .foldLeft(0)(_ + _) + .map(_ > 0) + div( + div(Button(_ => "Fetch list data", _.events.onClick.mapTo(()) --> fetchListDataBus.writer)), div( - div(Button(_ => "Fetch list data", _.onClick.mapTo(()) --> fetchListDataBus.writer)), - div( - BusyIndicator( - _.active <-- busyStates, - _ => - UList( - _ => width := "400px", - _ => children <-- fetchedData.map(data => data.map(pieceOfData => UList.Li(_ => pieceOfData))), - _.noDataText := "No Data" - ) - ) + BusyIndicator( + _.active <-- busyStates, + _ => + UList( + _ => width := "400px", + _ => children <-- fetchedData.map(data => data.map(pieceOfData => UList.item(_ => pieceOfData))), + _.noDataText := "No Data" + ) ) ) - } - ) + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/ButtonExample.scala b/demo/src/main/scala/demo/ButtonExample.scala index 12f7cc9..045a9bb 100644 --- a/demo/src/main/scala/demo/ButtonExample.scala +++ b/demo/src/main/scala/demo/ButtonExample.scala @@ -3,11 +3,13 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object ButtonExample extends Example("Button") { - def component: HtmlElement = { + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = { val exampleButtonContainerClass = "exampleButtonContainerClass" div( styleTag(s""" @@ -16,8 +18,8 @@ object ButtonExample extends Example("Button") { |} |""".stripMargin), h1("Button"), - DemoPanel( - "Basic Button", + DemoPanel("Basic Button")( + //-- Begin: Basic Button div( className := exampleButtonContainerClass, Button(_.design := ButtonDesign.Default, _ => "Default"), @@ -28,9 +30,10 @@ object ButtonExample extends Example("Button") { Button(_.design := ButtonDesign.Attention, _ => "Warning"), Button(_.design := ButtonDesign.Emphasized, _ => "Subscribe") ) + //-- End ), - DemoPanel( - "Button with Icon", + DemoPanel("Button with Icon")( + //-- Begin: Button with Icon div( className := exampleButtonContainerClass, Button(_.icon := IconName.employee, _ => "Add"), @@ -40,9 +43,10 @@ object ButtonExample extends Example("Button") { Button(_.design := ButtonDesign.Attention, _.icon := IconName.`message-warning`, _ => "Warning"), Button(_.design := ButtonDesign.Transparent, _.icon := IconName.accept, _ => "Transparent") ) + //-- End ), - DemoPanel( - "Icon Only Button", + DemoPanel("Icon Only Button")( + //-- Begin: Icon Only Button div( className := exampleButtonContainerClass, Button(_.icon := IconName.away), @@ -53,6 +57,7 @@ object ButtonExample extends Example("Button") { Button(_.icon := IconName.bookmark, _.design := ButtonDesign.Positive), Button(_.icon := IconName.cart, _.design := ButtonDesign.Transparent) ) + //-- End ) ) } diff --git a/demo/src/main/scala/demo/CalendarExample.scala b/demo/src/main/scala/demo/CalendarExample.scala new file mode 100644 index 0000000..5d743ba --- /dev/null +++ b/demo/src/main/scala/demo/CalendarExample.scala @@ -0,0 +1,62 @@ +package demo + +import be.doeraene.webcomponents.ui5.* +import be.doeraene.webcomponents.ui5.configkeys.* +import com.raquo.laminar.api.L.* +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} + +object CalendarExample extends Example("Calendar") { + + def component(using demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo): HtmlElement = div( + DemoPanel("Basic Calendar") { + //-- Begin: Basic Calendar + val maybeSelectedDateVar: Var[Option[Calendar.events.SelectedDatesChangeInfo]] = Var(None) + div( + Label(_ => + child.text <-- maybeSelectedDateVar.signal.map { + case None => "No selected date yet." + case Some(info) => s"Selected dates: ${info.values.zip(info.dates).mkString(", ")}." + } + ), + br(), + Calendar(_.events.onSelectedDatesChange.map(_.detail) --> maybeSelectedDateVar.writer.contramapSome) + ) + //-- End + }, + DemoPanel("Calendar with Minimum and Maximum Date & Format Pattern") { + //-- Begin: Calendar with Minimum and Maximum Date & Format Pattern + Calendar(_.minDateRaw := "7/7/2020", _.maxDateRaw := "20/10/2020", _.formatPattern := "dd/MM/yyyy") + //-- End + }, + DemoPanel("Calendar with Hidden Week Numbers") { + //-- Begin: Calendar with Hidden Week Numbers + Calendar(_.hideWeekNumbers := true) + //-- End + }, + DemoPanel("Calendar with Selection Mode Multiple") { + //-- Begin: Calendar with Selection Mode Multiple + Calendar(_.selectionMode := CalendarSelectionMode.Multiple) + //-- End + }, + DemoPanel("Calendar with Selection Mode Range") { + //-- Begin: Calendar with Selection Mode Range + Calendar(_.selectionMode := CalendarSelectionMode.Range) + //-- End + }, + DemoPanel("Other types of Calendar") { + //-- Begin: Other types of Calendar + + div(CalendarType.allValues.filterNot(_ == CalendarType.Gregorian).map { calendarType => + // Need to import the support for your calendar type. This has to be done at least once in your application + // (for example where your application starts, or the first place you use this calendar type) + calendarType.importObject + div( + Title.h3(_ => s"Type of calendar: ${calendarType.value}"), + Calendar(_.primaryCalendarType := calendarType) + ) + }) + //-- End + } + ) + +} diff --git a/demo/src/main/scala/demo/CardExample.scala b/demo/src/main/scala/demo/CardExample.scala index 7f0e1e8..3033d40 100644 --- a/demo/src/main/scala/demo/CardExample.scala +++ b/demo/src/main/scala/demo/CardExample.scala @@ -3,7 +3,7 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} object CardExample extends Example("Card") { @@ -16,9 +16,11 @@ object CardExample extends Example("Card") { private val contentPadding = "content-padding" - def component: HtmlElement = div( - DemoPanel( - "Card with List", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Card with List")( + //-- Begin: Card with List div( styleTag(s""" |.$cardContentClassName { @@ -57,7 +59,7 @@ object CardExample extends Example("Card") { .zip(MTG.manaSymbolsShortNames) .take(3) .map((name, shortName) => - UList.Li(_.image := MTG.manaSymbolsRefs(shortName), _.description := name, _ => name) + UList.item(_.image := MTG.manaSymbolsRefs(shortName), _.description := name, _ => name) ) ) ) @@ -83,15 +85,16 @@ object CardExample extends Example("Card") { .zip(MTG.manaSymbolsShortNames) .drop(3) .map((name, shortName) => - UList.Li(_.image := MTG.manaSymbolsRefs(shortName), _.description := name, _ => name) + UList.item(_.image := MTG.manaSymbolsRefs(shortName), _.description := name, _ => name) ) ) ) ) ) + //-- End ), - DemoPanel( - "Card with Table", + DemoPanel("Card with Table")( + //-- Begin: Card with Table div( styleTag(s""" |.$statusError {color: #b00;} @@ -137,7 +140,9 @@ object CardExample extends Example("Card") { ) ) ) - ) + //-- End + ), + mtgImageWarning ) } diff --git a/demo/src/main/scala/demo/CarouselExample.scala b/demo/src/main/scala/demo/CarouselExample.scala index 0f9dd42..07570cb 100644 --- a/demo/src/main/scala/demo/CarouselExample.scala +++ b/demo/src/main/scala/demo/CarouselExample.scala @@ -3,10 +3,11 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} object CarouselExample extends Example("Carousel") { + //-- Begin Common private def threeMagicWallpapers: List[Carousel.ModFunction] = List( _ => img( @@ -18,32 +19,39 @@ object CarouselExample extends Example("Carousel") { src := "https://media.magic.wizards.com/images/wallpaper/sparas_headquarters_kieran_yanner_1280x960_poozxbqpcw.jpg" ) ) + //-- End Common - def component: HtmlElement = div( + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( styleTag(""" |ui5-carousel > img { | max-width: 500px |} |""".stripMargin), - DemoPanel( - "Carousel With Single Item per Page", + DemoPanel("Carousel With Single Item per Page")( + //-- Begin: Carousel With Single Item per Page Carousel(threeMagicWallpapers: _*) + //-- End ), - DemoPanel( - "Carousel with Multiple items per Page", + DemoPanel("Carousel with Multiple items per Page")( + //-- Begin: Carousel with Multiple items per Page Carousel( _.itemsPerPageS := 1, _.itemsPerPageM := 2, _.itemsPerPageL := 2, _ => MTG.manaSymbolsShortNames.map(name => img(src := MTG.manaSymbolsRefs(name), alt := name)) ) + //-- End ), - DemoPanel( - "Carousel With Arrow Placement and Cyclic", + DemoPanel("Carousel With Arrow Placement and Cyclic")( + //-- Begin: Carousel With Arrow Placement and Cyclic Carousel( threeMagicWallpapers ++ List(_.arrowsPlacement := CarouselArrowsPlacement.Navigation, _.cyclic := true): _* ) - ) + //-- End + ), + mtgImageWarning ) } diff --git a/demo/src/main/scala/demo/CheckBoxExample.scala b/demo/src/main/scala/demo/CheckBoxExample.scala index 5a180e7..7e982f0 100644 --- a/demo/src/main/scala/demo/CheckBoxExample.scala +++ b/demo/src/main/scala/demo/CheckBoxExample.scala @@ -3,22 +3,26 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} object CheckBoxExample extends Example("CheckBox") { - def component: HtmlElement = div( - DemoPanel( - "Basic CheckBox", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + maxWidth := "calc(100% - 300px)", + DemoPanel("Basic CheckBox")( + //-- Begin: Basic CheckBox div( CheckBox(_.text := "Chocolate", _.checked := true), CheckBox(_.text := "Strawberry", _.checked := true), CheckBox(_.text := "Waffles", _.checked := true, _.valueState := ValueState.Error), CheckBox(_.text := "Cake", _.checked := true, _.valueState := ValueState.Warning) ) + //-- End ), - DemoPanel( - "CheckBox states", + DemoPanel("CheckBox states")( + //-- Begin: CheckBox states div( for { valueState <- ValueState.allValues @@ -38,9 +42,10 @@ object CheckBoxExample extends Example("CheckBox") { _.checked := true ) ) + //-- End ), - DemoPanel( - "CheckBox with Text Wrapping", + DemoPanel("CheckBox with Text Wrapping")( + //-- Begin: CheckBox with Text Wrapping div( CheckBox( _.text := "ui5-checkbox with 'wrapping-type=Normal' set and some long text.", @@ -53,39 +58,40 @@ object CheckBoxExample extends Example("CheckBox") { _ => width := "200px" ) ) + //-- End ), - DemoPanel( - "CheckBox with indeterminate", { - val texts = List("English", "German", "French") + DemoPanel("CheckBox with indeterminate") { + //-- Begin: CheckBox with indeterminate + val texts = List("English", "German", "French") - /** [[Var]] containing the state of all checkboxes. The few following lines help to keep that in sync. */ - val textsStatusesVar = Var(Map("English" -> true, "German" -> false, "French" -> false)) + /** [[Var]] containing the state of all checkboxes. The few following lines help to keep that in sync. */ + val textsStatusesVar = Var(Map("English" -> true, "German" -> false, "French" -> false)) - val textsStatusesUpdater = textsStatusesVar.updater[(String, Boolean)](_ + _) - val textsStatusesAllUpdater = textsStatusesVar.updater[Boolean]((map, b) => map.map((key, _) => key -> b)) + val textsStatusesUpdater = textsStatusesVar.updater[(String, Boolean)](_ + _) + val textsStatusesAllUpdater = textsStatusesVar.updater[Boolean]((map, b) => map.map((key, _) => key -> b)) - val numberOfCheckedSignal = textsStatusesVar.signal.map(_.values.count(identity)) + val numberOfCheckedSignal = textsStatusesVar.signal.map(_.values.count(identity)) - div( + div( + CheckBox( + _.text := "Select / deselect all", + _.indeterminate <-- numberOfCheckedSignal.map(count => count != 0 && count != 3), + _.checked <-- numberOfCheckedSignal.map(_ > 0), + // mapToChecked is possible but a bit hacky + _.events.onChange.mapToChecked --> textsStatusesAllUpdater + ), + hr(), + texts.map(text => CheckBox( - _.text := "Select / deselect all", - _.indeterminate <-- numberOfCheckedSignal.map(count => count != 0 && count != 3), - _.checked <-- numberOfCheckedSignal.map(_ > 0), - // mapToChecked is possible but a bit hacky - _.events.onChange.mapToChecked --> textsStatusesAllUpdater - ), - hr(), - texts.map(text => - CheckBox( - _.text := text, - _.checked <-- textsStatusesVar.signal.map(_(text)), - // map(_.target.checked) is completely typesafe - _.events.onChange.map(_.target.checked).map(text -> _) --> textsStatusesUpdater - ) + _.text := text, + _.checked <-- textsStatusesVar.signal.map(_(text)), + // map(_.target.checked) is completely typesafe + _.events.onChange.map(_.target.checked).map(text -> _) --> textsStatusesUpdater ) ) - } - ) + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/ColourPaletteExample.scala b/demo/src/main/scala/demo/ColourPaletteExample.scala index 86b07a1..4a982cb 100644 --- a/demo/src/main/scala/demo/ColourPaletteExample.scala +++ b/demo/src/main/scala/demo/ColourPaletteExample.scala @@ -3,11 +3,12 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} import be.doeraene.webcomponents.ui5.scaladsl.colour.Colour object ColourPaletteExample extends Example("ColourPalette") { + //-- Begin Common def someColourPaletteItems = List( Colour.fromString("darkblue"), Colour.fromString("pink"), @@ -22,11 +23,15 @@ object ColourPaletteExample extends Example("ColourPalette") { Colour.fromIntColour(0x5480e7), Colour.fromIntColour(0xff6699) ).map(colour => ColourPalette.item(_.value := colour)) + //-- End Common - def component: HtmlElement = div( - DemoPanel( - "Colour Palette", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Colour Palette")( + //-- Begin: Colour Palette ColourPalette(_ => someColourPaletteItems) + //-- End ) ) diff --git a/demo/src/main/scala/demo/ColourPalettePopoverExample.scala b/demo/src/main/scala/demo/ColourPalettePopoverExample.scala index aa79707..e1b2a02 100644 --- a/demo/src/main/scala/demo/ColourPalettePopoverExample.scala +++ b/demo/src/main/scala/demo/ColourPalettePopoverExample.scala @@ -3,43 +3,51 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} import be.doeraene.webcomponents.ui5.scaladsl.colour.Colour import org.scalajs.dom.HTMLElement import com.raquo.airstream.eventbus.EventBus object ColourPalettePopoverExample extends Example("ColourPalettePopover") { - private def colourPalettePopoverExample(withExtraFeatures: Boolean) = { - val openPopoverBus: EventBus[HTMLElement] = new EventBus - div( - Button( - _ => "Open ColourPalettePopover", - _.events.onClick.mapToEvent.map(_.target) --> openPopoverBus.writer - ), - ColourPalettePopover( - _ => - inContext(el => - openPopoverBus.events.map(el.ref -> _) --> Observer[(ColourPalettePopover.Ref, HTMLElement)](_ showAt _) - ), - _ => ColourPaletteExample.someColourPaletteItems, - _.showRecentColours := withExtraFeatures, - _.showMoreColours := withExtraFeatures, - _.showDefaultColour := withExtraFeatures, - _.defaultColour := Colour.green + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Color Palette Popover with recent colors, default color and more colors features") { + //-- Begin: Color Palette Popover with recent colors, default color and more colors features + val openPopoverBus: EventBus[HTMLElement] = new EventBus + div( + Button( + _ => "Open ColourPalettePopover", + _.events.onClick.mapToEvent.map(_.target) --> openPopoverBus.writer + ), + ColourPalettePopover( + _.showAtFromEvents(openPopoverBus.events), + _ => ColourPaletteExample.someColourPaletteItems, + _.showRecentColours := true, + _.showMoreColours := true, + _.showDefaultColour := true, + _.defaultColour := Colour.green + ) ) - ) - } - - def component: HtmlElement = div( - DemoPanel( - "Color Palette Popover with recent colors, default color and more colors features", - colourPalettePopoverExample(true) - ), - DemoPanel( - "Color Palette Popover without any additional features", - colourPalettePopoverExample(false) - ) + //-- End + }, + DemoPanel("Color Palette Popover without any additional features") { + //-- Begin: Color Palette Popover without any additional features + val openPopoverBus: EventBus[HTMLElement] = new EventBus + div( + Button( + _ => "Open ColourPalettePopover", + _.events.onClick.mapToEvent.map(_.target) --> openPopoverBus.writer + ), + ColourPalettePopover( + _.showAtFromEvents(openPopoverBus.events), + _ => ColourPaletteExample.someColourPaletteItems, + _.defaultColour := Colour.green + ) + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/ColourPickerExample.scala b/demo/src/main/scala/demo/ColourPickerExample.scala index 678c49f..f3c8a84 100644 --- a/demo/src/main/scala/demo/ColourPickerExample.scala +++ b/demo/src/main/scala/demo/ColourPickerExample.scala @@ -3,26 +3,28 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} import be.doeraene.webcomponents.ui5.scaladsl.colour.Colour object ColourPickerExample extends Example("ColourPicker") { - def component: HtmlElement = div( - DemoPanel( - "Pick colour", { - val maybeChosenColourVar: Var[Option[Colour]] = Var(Option.empty) + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Pick colour") { + //-- Begin: Pick Colour + val maybeChosenColourVar: Var[Option[Colour]] = Var(Option.empty) + div( + ColourPicker(_.events.onChange.map(_.target.colour).map(Some(_)) --> maybeChosenColourVar.writer), div( - ColourPicker(_.events.onChange.map(_.target.colour).map(Some(_)) --> maybeChosenColourVar.writer), - div( - child.text <-- maybeChosenColourVar.signal.map { - case Some(colour) => s"You have chosen colour ${colour.rgba}." - case None => "Chose a colour." - } - ) + child.text <-- maybeChosenColourVar.signal.map { + case Some(colour) => s"You have chosen colour ${colour.rgba}." + case None => "Chose a colour." + } ) - } - ) + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/ComboBoxExample.scala b/demo/src/main/scala/demo/ComboBoxExample.scala index 65c93a9..3fbab56 100644 --- a/demo/src/main/scala/demo/ComboBoxExample.scala +++ b/demo/src/main/scala/demo/ComboBoxExample.scala @@ -3,7 +3,7 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} object ComboBoxExample extends Example("ComboBox") { @@ -19,24 +19,28 @@ object ComboBoxExample extends Example("ComboBox") { private val someOtherCountries = List("Argentina", "Australia", "Austria", "Barhain", "Belgium", "Brazil", "Canada", "Chile") - def component: HtmlElement = div( - DemoPanel( - "Basic Example", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Example")( + //-- Begin: Basic Example div( ValueState.allValues.map(valueState => ComboBox(_.placeholder := "Enter value", _.valueState := valueState, someItems) ) ) + //-- End ), - DemoPanel( - "Disabled and Readonly properties", + DemoPanel("Disabled and Readonly properties")( + //-- Begin: Disabled and Readonly properties div( ComboBox(_.value := "Disabled", _.disabled := true, someItems), ComboBox(_.value := "Readonly", _.readonly := true, someItems) ) + //-- End ), - DemoPanel( - "Filters (StartsWithPerTerm(default), StartsWith, Contains)", + DemoPanel("Filters (StartsWithPerTerm(default), StartsWith, Contains)")( + //-- Begin: Filters (StartsWithPerTerm(default), StartsWith, Contains) div( ComboBoxFilter.allValues.map(filter => ComboBox( @@ -46,9 +50,10 @@ object ComboBoxExample extends Example("ComboBox") { ) ) ) + //-- End ), - DemoPanel( - "ComboBox with Two-Column Layout Items", + DemoPanel("ComboBox with Two-Column Layout Items")( + //-- Begin: ComboBox with Two-Column Layout Items ComboBox( _.placeholder := "Two-column layout", _ => @@ -61,9 +66,10 @@ object ComboBoxExample extends Example("ComboBox") { ) ) ) + //-- End ), - DemoPanel( - "ComboBox with Grouping of Items", + DemoPanel("ComboBox with Grouping of Items")( + //-- Begin: ComboBox with Grouping of Items ComboBox( _.placeholder := "ComboBox with grouping of suggestions", _ => @@ -76,6 +82,7 @@ object ComboBoxExample extends Example("ComboBox") { ) ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/DatePickerExample.scala b/demo/src/main/scala/demo/DatePickerExample.scala index c4851d1..5e949a0 100644 --- a/demo/src/main/scala/demo/DatePickerExample.scala +++ b/demo/src/main/scala/demo/DatePickerExample.scala @@ -3,44 +3,49 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} import java.time.LocalDate object DatePickerExample extends Example("DatePicker") { - def component: HtmlElement = div( + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( MessageStrip( _.design := MessageStripDesign.Warning, _ => "Note: you must provide an implementation for java.time classes in order to use the DatePicker. " + "For example using the scala-java-time library." ), - DemoPanel( - "Basic DatePicker", + DemoPanel("Basic DatePicker")( + //-- Begin: Basic DatePicker DatePicker() + //-- End ), - DemoPanel( - "DatePicker with Placeholder, Tooltip, Events, ValueState and valueStateMessage", { - val validityStateBus: EventBus[Boolean] = new EventBus - DatePicker( - _.placeholder := "Delivery date...", - _.slots.valueStateMessage := div("The value is not valid. Please provide a valid value."), - _.valueState <-- validityStateBus.events.map(if _ then ValueState.None else ValueState.Error), - _.events.onChange.map(_.detail.valid) --> validityStateBus.writer - ) - } - ), - DemoPanel( - "DatePicker with Minimum and Maximum Date - 1/1/2020 - 4/5/2020 format-pattern='yyyy-MM-dd'", + DemoPanel("DatePicker with Placeholder, Tooltip, Events, ValueState and valueStateMessage") { + //-- Begin: DatePicker with Placeholder, Tooltip, Events, ValueState and valueStateMessage + val validityStateBus: EventBus[Boolean] = new EventBus + DatePicker( + _.placeholder := "Delivery date...", + _.slots.valueStateMessage := div("The value is not valid. Please provide a valid value."), + _.valueState <-- validityStateBus.events.map(if _ then ValueState.None else ValueState.Error), + _.events.onChange.map(_.detail.valid) --> validityStateBus.writer + ) + //-- End + }, + DemoPanel("DatePicker with Minimum and Maximum Date - 1/1/2020 - 4/5/2020 format-pattern='yyyy-MM-dd'")( + //-- Begin: DatePicker with Minimum and Maximum Date - 1/1/2020 - 4/5/2020 format-pattern='yyyy-MM-dd' DatePicker( _.formatPattern := "yyyy-MM-dd", _.minDate := LocalDate.of(2020, 1, 1), _.maxDate := LocalDate.of(2020, 5, 4) ) + //-- End ), - DemoPanel( - "DatePicker with Japanese Calendar Type", + DemoPanel("DatePicker with Japanese Calendar Type")( + //-- Begin: DatePicker with Japanese Calendar Type DatePicker(_.primaryCalendarType := CalendarType.Japanese) + //-- End ) ) diff --git a/demo/src/main/scala/demo/DateRangePickerExample.scala b/demo/src/main/scala/demo/DateRangePickerExample.scala index f60d586..bcf39df 100644 --- a/demo/src/main/scala/demo/DateRangePickerExample.scala +++ b/demo/src/main/scala/demo/DateRangePickerExample.scala @@ -3,10 +3,38 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} object DateRangePickerExample extends Example("DateRangePicker") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic DateRangePicker") { + //-- Begin: Basic DateRangePicker + DateRangePicker() + //-- End + }, + DemoPanel("DateRangePicker with Minimum and Maximum Date - 1/1/2020 - 4/5/2020 format-pattern='dd/MM/yyyy'") { + //-- Begin: DateRangePicker with Minimum and Maximum Date - 1/1/2020 - 4/5/2020 format-pattern='dd/MM/yyyy' + DateRangePicker(_.minDateRaw := "1/1/2020", _.maxDateRaw := "4/5/2020", _.formatPattern := "dd/MM/yyyy") + //-- End + }, + DemoPanel("DateRangePicker with format-pattern='long'") { + //-- Begin: DateRangePicker with format-pattern='long' + DateRangePicker(_.formatPattern := "long") + //-- End + }, + DemoPanel("Disabled DateRangePicker") { + //-- Begin: Disabled DateRangePicker + DateRangePicker(_.disabled := true, _.value := "Mar 31, 2021 - Apr 9, 2021") + //-- End + }, + DemoPanel("Readonly DateRangePicker") { + //-- Begin: Readonly DateRangePicker + DateRangePicker(_.readonly := true, _.value := "Mar 31, 2021 - Apr 9, 2021") + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/DateTimePickerExample.scala b/demo/src/main/scala/demo/DateTimePickerExample.scala index d9a2e46..2648eae 100644 --- a/demo/src/main/scala/demo/DateTimePickerExample.scala +++ b/demo/src/main/scala/demo/DateTimePickerExample.scala @@ -3,10 +3,55 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} +import java.time.format.DateTimeFormatter +import java.time.LocalDateTime +import scala.util.Try object DateTimePickerExample extends Example("DateTimePicker") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("DateTimePicker") { + //-- Begin: DateTimePicker + val localDateTimeFormatter = DateTimeFormatter.ofPattern("d MMM y, HH:mm:ss") + val valueUpdateBus: EventBus[LocalDateTime] = new EventBus + div( + Label(_ => + child.text <-- valueUpdateBus.events.startWithNone.map { + case None => "No value selected yet" + case Some(dateTime) => s"Selected value: $dateTime" + } + ), + br(), + DateTimePicker( + _.events.onInput + .map(_.detail.value) + // Wrap in Try because input triggers even if element is not valid + .map(value => Try(LocalDateTime.parse(value, localDateTimeFormatter)).toOption) + .collect { case Some(dateTime) => dateTime } --> valueUpdateBus.writer, + _.events.onChange + .map(_.detail.value) // no need for Try shenanigans, change does not trigger for invalid values + .map(LocalDateTime.parse(_, localDateTimeFormatter)) --> valueUpdateBus.writer + ) + ) + //-- End + }, + DemoPanel("DateTimePicker with format-pattern") { + //-- Begin: DateTimePicker with format-pattern + div( + DateTimePicker(_.formatPattern := "dd/MM/yyyy, hh:mm aa", _.value := "13/04/2020, 09:16 AM"), + DateTimePicker(_.formatPattern := "yyyy-MM-dd-hh:mm:ss aa", _.value := "2020-04-13-04:16:16 AM"), + DateTimePicker(_.formatPattern := "dd/MM/yyyy, hh:mm:ss aa", _.value := "13/04/2020, 03:16:16 AM") + ) + //-- End + }, + DemoPanel("DateTimePicker in states") { + //-- Begin: DateTimePicker in states + div(ValueState.allValues.map(state => DateTimePicker(_.valueState := state))) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/DialogExample.scala b/demo/src/main/scala/demo/DialogExample.scala index 088d5f4..3b53268 100644 --- a/demo/src/main/scala/demo/DialogExample.scala +++ b/demo/src/main/scala/demo/DialogExample.scala @@ -3,106 +3,108 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object DialogExample extends Example("Dialog") { - def component: HtmlElement = div( - DemoPanel( - "Basic Dialog", { - val openDialogBus: EventBus[Boolean] = new EventBus + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Dialog") { + //-- Begin: Basic Dialog + val openDialogBus: EventBus[Boolean] = new EventBus + div( + styleTagForLoginFormClass, + Button( + _.design := ButtonDesign.Emphasized, + _ => "Open Dialog", + _.events.onClick.mapTo(true) --> openDialogBus.writer + ), div( - styleTagForLoginFormClass, - Button( - _.design := ButtonDesign.Emphasized, - _ => "Open Dialog", - _.events.onClick.mapTo(true) --> openDialogBus.writer - ), - div( - MessageStrip( - _.design := MessageStripDesign.Information, - _ => - "The opening of this dialog works using an `EventBus`. " + - "Clicking on the 'Open Dialog' button writes to the bus, and the Dialog listens to it." - ) - ), - Dialog( - _ => - inContext(el => openDialogBus.events --> Observer[Boolean](if _ then el.ref.show() else el.ref.close())), - _.headerText := "Register Form", - _ => - section( - className := loginFormClass, - div( - Label(_.forId := "username", _.required := true, _ => "Username:"), - Input(_.id := "username") - ), - div( - Label(_.forId := "password", _.required := true, _ => "Password:"), - Input(_.id := "password", _.tpe := InputType.Password, _.valueState := ValueState.Error) - ), - div( - Label(_.forId := "email", _.required := true, _ => "Email:"), - Input(_.id := "email", _.tpe := InputType.Email) - ) - ), - _.slots.footer := div( - div(flex := "1"), - Button( - _.design := ButtonDesign.Emphasized, - _ => "Register", - _.onClick.mapTo(false) --> openDialogBus.writer - ) - ) - ) - ) - } - ), - DemoPanel( - "Draggable and Resizable Dialog", { - val dialogId = "the-dialog-id" - div( - Button( - _ => "Open Draggable/Resizable dialog", - _.events.onClick.mapTo(Dialog.getDialogById(dialogId)) --> Observer[Option[Dialog.Ref]] { - case Some(dialog) => dialog.show() - case None => throw new IllegalStateException(s"The Dialog with id $dialogId does not exist.") - } - ), MessageStrip( _.design := MessageStripDesign.Information, - _ => "The opening of this dialog works by finding the dialog by id (with `Dialog.getDialogById`)." - ), - Dialog( - _.id := dialogId, - _.headerText := "Draggable/Resizable dialog", _ => - section( - "Resize this dialog by dragging it by its resize handle.", - br(), - "This feature is available only on Desktop", - br(), - "Move this dialog around the screen by dragging it by its header", - br(), - "This feature is available only on Desktop" + "The opening of this dialog works using an `EventBus`. " + + "Clicking on the 'Open Dialog' button writes to the bus, and the Dialog listens to it." + ) + ), + Dialog( + _.showFromEvents(openDialogBus.events.filter(identity).mapTo(())), + _.closeFromEvents(openDialogBus.events.map(!_).filter(identity).mapTo(())), + _.headerText := "Register Form", + _ => + section( + className := loginFormClass, + div( + Label(_.forId := "username", _.required := true, _ => "Username:"), + Input(_.id := "username") + ), + div( + Label(_.forId := "password", _.required := true, _ => "Password:"), + Input(_.id := "password", _.tpe := InputType.Password, _.valueState := ValueState.Error) ), - _.slots.footer := div( - div(flex := "1"), - Button( - _.design := ButtonDesign.Emphasized, - _ => "Close", - _.onClick.mapTo(Dialog.getDialogById(dialogId)) --> Observer[Option[Dialog.Ref]] { - case Some(dialog) => dialog.close() - case None => throw new IllegalStateException(s"Dialog with id $dialogId does not exist.") - } + div( + Label(_.forId := "email", _.required := true, _ => "Email:"), + Input(_.id := "email", _.tpe := InputType.Email) ) ), - _.draggable := true, - _.resizable := true + _.slots.footer := div( + div(flex := "1"), + Button( + _.design := ButtonDesign.Emphasized, + _ => "Register", + _.events.onClick.mapTo(false) --> openDialogBus.writer + ) ) ) - } - ) + ) + //-- End + }, + DemoPanel("Draggable and Resizable Dialog") { + //-- Begin: Draggable and Resizable Dialog + val dialogId = "the-dialog-id" + div( + Button( + _ => "Open Draggable/Resizable dialog", + _.events.onClick.mapTo(Dialog.getDialogById(dialogId)) --> Observer[Option[Dialog.Ref]] { + case Some(dialog) => dialog.show() + case None => throw new IllegalStateException(s"The Dialog with id $dialogId does not exist.") + } + ), + MessageStrip( + _.design := MessageStripDesign.Information, + _ => "The opening of this dialog works by finding the dialog by id (with `Dialog.getDialogById`)." + ), + Dialog( + _.id := dialogId, + _.headerText := "Draggable/Resizable dialog", + _ => + section( + "Resize this dialog by dragging it by its resize handle.", + br(), + "This feature is available only on Desktop", + br(), + "Move this dialog around the screen by dragging it by its header", + br(), + "This feature is available only on Desktop" + ), + _.slots.footer := div( + div(flex := "1"), + Button( + _.design := ButtonDesign.Emphasized, + _ => "Close", + _.events.onClick.mapTo(Dialog.getDialogById(dialogId)) --> Observer[Option[Dialog.Ref]] { + case Some(dialog) => dialog.close() + case None => throw new IllegalStateException(s"Dialog with id $dialogId does not exist.") + } + ) + ), + _.draggable := true, + _.resizable := true + ) + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/DynamicSideContentExample.scala b/demo/src/main/scala/demo/DynamicSideContentExample.scala index 72678ff..162fcc9 100644 --- a/demo/src/main/scala/demo/DynamicSideContentExample.scala +++ b/demo/src/main/scala/demo/DynamicSideContentExample.scala @@ -3,10 +3,123 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object DynamicSideContentExample extends Example("DynamicSideContent") { - def component: HtmlElement = missing + val mainContent = + "103.1. At the start of a game, the players determine which one of them will choose who takes the first turn. In " + + "the first game of a match (including a single-game match), the players may use any mutually agreeable method " + + "(flipping a coin, rolling dice, etc.) to do so. In a match of several games, the loser of the previous game " + + "chooses who takes the first turn. If the previous game was a draw, the player who made the choice in that game" + + " makes the choice in this game. The player chosen to take the first turn is the starting player. The game’s " + + "default turn order begins with the starting player and proceeds clockwise." + + val sideContent = + "103.4. Each player draws a number of cards equal to their starting hand size, which is normally seven. (Some " + + "effects can modify a player’s starting hand size.) A player who is dissatisfied with their initial hand may " + + "take a mulligan. First, the starting player declares whether they will take a mulligan. Then each other player " + + "in turn order does the same. Once each player has made a declaration, all players who decided to take " + + "mulligans do so at the same time. To take a mulligan, a player shuffles the cards in their hand back into their " + + "library, draws a new hand of cards equal to their starting hand size, then puts a number of those cards equal " + + "to the number of times that player has taken a mulligan on the bottom of their library in any order. Once a " + + "player chooses not to take a mulligan, the remaining cards become that player’s opening hand, and that player " + + "may not take any further mulligans. This process is then repeated until no player takes a mulligan. A player" + + " can take mulligans until their opening hand would be zero cards, after which they may not take further" + + " mulligans." + + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Dynamic Side Content with default properties") { + //-- Begin: Dynamic Side Content with default properties + DynamicSideContent( + _ => div(h1("Main Content"), p(mainContent)), + _.slots.sideContent := div(h1("Side Content"), p(sideContent)) + ) + //-- End + }, + DemoPanel("Dynamic Side Content with hideMainContent set") { + //-- Begin: Dynamic Side Content with hideMainContent set + DynamicSideContent( + _.hideMainContent := true, + _ => div(h1("Main Content"), p(mainContent)), + _.slots.sideContent := div(h1("Side Content"), p(sideContent)) + ) + //-- End + }, + DemoPanel("Dynamic Side Content with hideSideContent set") { + //-- Begin: Dynamic Side Content with hideSideContent set + DynamicSideContent( + _.hideSideContent := true, + _ => div(h1("Main Content"), p(mainContent)), + _.slots.sideContent := div(h1("Side Content"), p(sideContent)) + ) + //-- End + }, + DemoPanel("Dynamic Side Content with equalSplit set") { + //-- Begin: Dynamic Side Content with equalSplit set + DynamicSideContent( + _.equalSplit := true, + _ => div(h1("Main Content"), p(mainContent)), + _.slots.sideContent := div(h1("Side Content"), p(sideContent)) + ) + //-- End + }, + DemoPanel("Dynamic Side Content with sideContentPosition='Start'") { + //-- Begin: Dynamic Side Content with sideContentPosition='Start' + DynamicSideContent( + _.sideContentPosition := SideContentPosition.Start, + _ => div(h1("Main Content"), p(mainContent)), + _.slots.sideContent := div(h1("Side Content"), p(sideContent)) + ) + //-- End + }, + DemoPanel("Dynamic Side Content with sideContentFallDown='BelowXL'") { + //-- Begin: Dynamic Side Content with sideContentFallDown='BelowXL' + DynamicSideContent( + _.sideContentFallDown := SideContentFallDown.BelowXL, + _ => div(h1("Main Content"), p(mainContent)), + _.slots.sideContent := div(h1("Side Content"), p(sideContent)) + ) + //-- End + }, + DemoPanel("Dynamic Side Content with sideContentVisibility='ShowAboveM'") { + //-- Begin: Dynamic Side Content with sideContentVisibility='ShowAboveM' + DynamicSideContent( + _.sideContentVisibility := SideContentVisibility.ShowAboveM, + _ => div(h1("Main Content"), p(mainContent)), + _.slots.sideContent := div(h1("Side Content"), p(sideContent)) + ) + //-- End + }, + DemoPanel("Dynamic Side Content - toggle contents on mobile device (S size)") { + //-- Begin: Dynamic Side Content - toggle contents on mobile device (S size) + val toggleContentsBus: EventBus[Unit] = new EventBus + + Page( + _ => height := "500px", + _ => maxWidth := "360px", + _.floatingFooter := true, + _.hideFooter := false, + //_ => maxWidth := "360px", + _ => + DynamicSideContent( + _ => inContext(el => toggleContentsBus.events --> Observer[Any](_ => el.ref.toggleContents())), + _ => div(h1("Main Content"), p(mainContent)), + _.slots.sideContent := div(h1("Side Content"), p(sideContent)) + ), + _.slots.footer := Bar( + _.design := BarDesign.FloatingFooter, + _.slots.endContent := Button( + _.design := ButtonDesign.Positive, + _ => "Toggle Contents", + _.events.onClick.mapTo(()) --> toggleContentsBus.writer + ) + ) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/EntryPoint.scala b/demo/src/main/scala/demo/EntryPoint.scala index d05b230..f00cb6b 100644 --- a/demo/src/main/scala/demo/EntryPoint.scala +++ b/demo/src/main/scala/demo/EntryPoint.scala @@ -8,13 +8,17 @@ import demo.helpers.Example object EntryPoint { def main(args: Array[String]): Unit = { + ResponsivePopover val componentsDemo: List[Example] = List( AvatarExample, + AvatarGroupExample, BadgeExample, BarExample, + BarcodeScannerDialogExample, BreadcrumbsExample, BusyIndicatorExample, ButtonExample, + CalendarExample, CardExample, CarouselExample, CheckBoxExample, @@ -87,25 +91,29 @@ object EntryPoint { else div( display := "flex", - SideNavigation( - _.events.onSelectionChange.map(_.detail.item.dataset.get("componentName")) --> Observer[Option[String]] { - case Some(componentName) => dom.document.location.href = s"/$componentName" - case None => throw new IllegalArgumentException(s"This item did not have data 'componentName'.") - }, - _ => - componentsDemo.map(example => - SideNavigation.item( - _.text := example.name, - _ => width := "200px", - _ => dataAttr("component-name") := example.name, - _.selected := (example.name == componentName) - ) - ), - _ => height := "100vh", - _ => overflowY := "auto" + div( + width := "300px", + SideNavigation( + _.events.onSelectionChange.map(_.detail.item.dataset.get("componentName")) --> Observer[Option[String]] { + case Some(componentName) => dom.document.location.href = s"/$componentName" + case None => throw new IllegalArgumentException(s"This item did not have data 'componentName'.") + }, + _ => + componentsDemo.map(example => + SideNavigation.item( + _.text := example.name, + _ => width := "200px", + _ => dataAttr("component-name") := example.name, + _.selected := (example.name == componentName) + ) + ), + _ => height := "100vh", + _ => overflowY := "auto" + ) ), div( padding := "10px", + maxWidth := "calc(100% - 320px)", componentsDemo.find(_.name == componentName).map(_.completeComponent).getOrElse(div("Not Found")) ) ) diff --git a/demo/src/main/scala/demo/FileUploaderExample.scala b/demo/src/main/scala/demo/FileUploaderExample.scala index 5108dec..8f9ed82 100644 --- a/demo/src/main/scala/demo/FileUploaderExample.scala +++ b/demo/src/main/scala/demo/FileUploaderExample.scala @@ -3,10 +3,73 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} +import org.scalajs.dom object FileUploaderExample extends Example("FileUploader") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Upload multiple images") { + //-- Begin: Upload multiple images + // contains the selected images. Starts with None before the user chose files + val selectedImagesVar: Var[Option[List[dom.File]]] = Var(None) + + div( + div( + FileUploader( + _.accept := List("image/*"), + _.multiple := true, + _.events.onChange.map(_.target.files) --> selectedImagesVar.writer.contramapSome, + _ => Button(_ => "Upload Images", _.icon := IconName.upload) + ) + ), + div( + className := "results-container", + styleTag(""" + |.results-container > img { + | margin: 0.5rem; + |} + |""".stripMargin), + children <-- selectedImagesVar.signal.changes.collect { case Some(files) => files }.map { + case Nil => List(span("No files selected.")) + case files => + files.map { file => + img( + widthAttr := 100, + heightAttr := 100, + src := dom.URL.createObjectURL(file), + inContext(el => onLoad --> Observer[Any](_ => dom.URL.revokeObjectURL(el.ref.src))) + ) + } + } + ) + ) + //-- End + }, + DemoPanel("File Uploader With No Input") { + //-- Begin: File Uploader With No Input + FileUploader(_.hideInput := true, _ => Button(_ => "Upload File")) + //-- End + }, + DemoPanel("Custom File Uploaders") { + //-- Begin: Custom File Uploaders + div( + FileUploader(_.hideInput := true, _ => Avatar(_.icon := IconName.upload)), + FileUploader(_.hideInput := true, _ => Badge(_ => "Upload File")) + ) + //-- End + }, + DemoPanel("Button With Icon File Uploader") { + //-- Begin: Button With Icon File Uploader + div( + FileUploader(_ => Button(_.icon := IconName.upload, _ => "Upload")), + FileUploader(_ => Button(_.icon := IconName.upload, _.iconEnd := true, _ => "Upload")), + FileUploader(_ => Button(_.icon := IconName.upload, _.iconOnly := true)) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/FlexibleColumnLayoutExample.scala b/demo/src/main/scala/demo/FlexibleColumnLayoutExample.scala index 69cd7c8..6a114ff 100644 --- a/demo/src/main/scala/demo/FlexibleColumnLayoutExample.scala +++ b/demo/src/main/scala/demo/FlexibleColumnLayoutExample.scala @@ -3,34 +3,36 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} object FlexibleColumnLayoutExample extends Example("FlexibleColumnLayout") { import MTG.{cards, Card} - def component: HtmlElement = div( - DemoPanel( - "FlexibleColumnLayout - One Initial Column", { + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("FlexibleColumnLayout - One Initial Column") { + //-- Begin: FlexibleColumnLayout - One Initial Column - /** Feed in here whatever layout you want to give to the FlexibleColumnLayout */ - val layoutBus: EventBus[FCLLayout] = new EventBus + /** Feed in here whatever layout you want to give to the FlexibleColumnLayout */ + val layoutBus: EventBus[FCLLayout] = new EventBus - val maybeSelectedCardVar = Var(Option.empty[Card]) + val maybeSelectedCardVar = Var(Option.empty[Card]) - def startColumnListItem(card: Card): HtmlElement = UList.Li( - _ => card.name, - _.description := card.tpe, - _.additionalText := s"Cost: ${card.cost}", - _.iconEnd := true, - _.icon := IconName.`slim-arrow-right`, - _ => dataAttr("card-name") := card.name - ) + def startColumnListItem(card: Card): HtmlElement = UList.item( + _ => card.name, + _.description := card.tpe, + _.additionalText := s"Cost: ${card.cost}", + _.iconEnd := true, + _.icon := IconName.`slim-arrow-right`, + _ => dataAttr("card-name") := card.name + ) - def cardFromName(name: String): Option[Card] = cards.find(_.name == name) + def cardFromName(name: String): Option[Card] = cards.find(_.name == name) - div( - styleTag(""" + div( + styleTag(""" |:host([description]) .ui5-li-root { | padding: 1rem; |} @@ -45,47 +47,47 @@ object FlexibleColumnLayoutExample extends Example("FlexibleColumnLayout") { | box-sizing: border-box; |} |""".stripMargin), - FlexibleColumnLayout( - _.layout <-- layoutBus.events.startWith(FCLLayout.OneColumn), - _.slots.startColumn := div( - ShellBar(_.primaryTitle := "Magic"), - UList( - _ => height := "500px", - _.headerText := "Power Nine", - _ => cards.filter(_.comment == "Power Nine").map(startColumnListItem), - _.events.onItemClick - .map(event => - for { - cardName <- event.detail.item.dataset.get("cardName") - card <- cardFromName(cardName) - } yield card - ) --> maybeSelectedCardVar.writer - ) - ), - _.slots.midColumn <-- maybeSelectedCardVar.signal.changes.collect { case Some(card) => card }.map { card => + FlexibleColumnLayout( + _.layout <-- layoutBus.events.startWith(FCLLayout.OneColumn), + _.slots.startColumn := div( + ShellBar(_.primaryTitle := "Magic"), + UList( + _ => height := "500px", + _.headerText := "Power Nine", + _ => cards.filter(_.comment == "Power Nine").map(startColumnListItem), + _.events.onItemClick + .map(event => + for { + cardName <- event.detail.item.dataset.get("cardName") + card <- cardFromName(cardName) + } yield card + ) --> maybeSelectedCardVar.writer + ) + ), + _.slots.midColumn <-- maybeSelectedCardVar.signal.changes.collect { case Some(card) => card }.map { card => + div( div( - div( - display := "flex", - alignItems := "center", - Button( - _.icon := IconName.`slim-arrow-left`, - _.events.onClick.mapTo(Option.empty[Card]) --> maybeSelectedCardVar.writer, - _ => marginRight := "1em", - _.design := ButtonDesign.Transparent - ), - h1(card.name) + display := "flex", + alignItems := "center", + Button( + _.icon := IconName.`slim-arrow-left`, + _.events.onClick.mapTo(Option.empty[Card]) --> maybeSelectedCardVar.writer, + _ => marginRight := "1em", + _.design := ButtonDesign.Transparent ), - img(src := MTG.cardImages(card.name)) - ) - }, - _ => - maybeSelectedCardVar.signal.changes.map(maybeCard => - if maybeCard.isDefined then FCLLayout.TwoColumnsMidExpanded else FCLLayout.OneColumn - ) --> layoutBus.writer - ) + h1(card.name) + ), + img(src := MTG.cardImages(card.name)) + ) + }, + _ => + maybeSelectedCardVar.signal.changes.map(maybeCard => + if maybeCard.isDefined then FCLLayout.TwoColumnsMidExpanded else FCLLayout.OneColumn + ) --> layoutBus.writer ) - } - ) + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/IconExample.scala b/demo/src/main/scala/demo/IconExample.scala index 799ca36..e00f8ab 100644 --- a/demo/src/main/scala/demo/IconExample.scala +++ b/demo/src/main/scala/demo/IconExample.scala @@ -3,19 +3,20 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object IconExample extends Example("Icon") { - def component: HtmlElement = div( - DemoPanel( - "Basic Icons", - div( - IconName.allValues.take(10).map(name => Icon(_.name := name, _ => marginRight := "5px")) - ) + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Icons")( + //-- Begin: Basic Icons + div(IconName.allValues.take(10).map(name => Icon(_.name := name, _ => marginRight := "5px"))) + //-- End ), - DemoPanel( - "Customized Icons", + DemoPanel("Customized Icons")( + //-- Begin: Customized Icons div( IconName.allValues.reverse .take(3) @@ -34,6 +35,7 @@ object IconExample extends Example("Icon") { ) ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/IllustratedMessageExample.scala b/demo/src/main/scala/demo/IllustratedMessageExample.scala index 4dece9f..2cb1942 100644 --- a/demo/src/main/scala/demo/IllustratedMessageExample.scala +++ b/demo/src/main/scala/demo/IllustratedMessageExample.scala @@ -3,13 +3,15 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object IllustratedMessageExample extends Example("IllustratedMessage") { - def component: HtmlElement = div( - DemoPanel( - "Illustrated Message", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Illustrated Message")( + //-- Begin: Illustrated Message IllustratedMessage( _.name := IllustrationMessageType.PageNotFound, _ => @@ -18,31 +20,32 @@ object IllustratedMessageExample extends Example("IllustratedMessage") { Button(_ => "Action 2") ) ) + //-- End ), - DemoPanel( - "Illustrated Message in dialog", { - val dialogShowActionBus: EventBus[Boolean] = new EventBus - div( - Button(_ => "Open Dialog", _.events.onClick.mapTo(true) --> dialogShowActionBus.writer), - Dialog( - _.headerText := "Error", - _ => - inContext(el => - dialogShowActionBus.events --> Observer[Boolean](if _ then el.ref.show() else el.ref.close()) - ), - _ => IllustratedMessage(_.name := IllustrationMessageType.ErrorScreen), - _.slots.footer := Bar( - _.design := BarDesign.Footer, - _.slots.endContent := Button( - _.design := ButtonDesign.Emphasized, - _ => "Close", - _.events.onClick.mapTo(false) --> dialogShowActionBus.writer - ) + DemoPanel("Illustrated Message in dialog") { + //-- Begin: Illustrated Message in dialog + val dialogShowActionBus: EventBus[Boolean] = new EventBus + div( + Button(_ => "Open Dialog", _.events.onClick.mapTo(true) --> dialogShowActionBus.writer), + Dialog( + _.headerText := "Error", + _ => + inContext(el => + dialogShowActionBus.events --> Observer[Boolean](if _ then el.ref.show() else el.ref.close()) + ), + _ => IllustratedMessage(_.name := IllustrationMessageType.ErrorScreen), + _.slots.footer := Bar( + _.design := BarDesign.Footer, + _.slots.endContent := Button( + _.design := ButtonDesign.Emphasized, + _ => "Close", + _.events.onClick.mapTo(false) --> dialogShowActionBus.writer ) ) ) - } - ) + ) + //-- End + } // todo: uncomment this example when we have a high enough sap ui5 version. // DemoPanel( // "Illustrated Message with link in subtitle", diff --git a/demo/src/main/scala/demo/InputExample.scala b/demo/src/main/scala/demo/InputExample.scala index c828f91..1e99e80 100644 --- a/demo/src/main/scala/demo/InputExample.scala +++ b/demo/src/main/scala/demo/InputExample.scala @@ -3,108 +3,113 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object InputExample extends Example("Input") { private val countries = List("Argentina", "Belgium", "Bulgaria", "Canada", "Columbia", "Croatia", "Denmark") - def component: HtmlElement = div( - DemoPanel( - "Basic Input", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Input")( + //-- Begin: Basic Input div( Input(_.showClearIcon := true, _.value := "Input"), Input(_.readonly := true, _.value := "Readonly Input"), Input(_.disabled := true, _.value := "Disabled Input") ) + //-- End ), - DemoPanel( - "Input With Suggestions (note: the usage depends on the framework you are using)", { + DemoPanel("Input With Suggestions (note: the usage depends on the framework you are using)") { + //-- Begin: Input With Suggestions (note: the usage depends on the framework you are using) - val filterValueBus: EventBus[String] = new EventBus + val filterValueBus: EventBus[String] = new EventBus - val suggestions = - filterValueBus.events - .map(input => - if input.trim.isEmpty then Nil - else countries.filter(_.toLowerCase.contains(input.toLowerCase)) - ) - .startWith(Nil) - .map( - _.map(country => - Input.suggestion( - _.icon := IconName.world, - _.additionalText := "explore", - _.additionalTextState := ValueState.Success, - _.description := "travel the world", - _.text := country - ) + val suggestions = + filterValueBus.events + .map(input => + if input.trim.isEmpty then Nil + else countries.filter(_.toLowerCase.contains(input.toLowerCase)) + ) + .startWith(Nil) + .map( + _.map(country => + Input.suggestion( + _.icon := IconName.world, + _.additionalText := "explore", + _.additionalTextState := ValueState.Success, + _.description := "travel the world", + _.text := country ) ) + ) - Input( - _.showSuggestions := true, - _.showClearIcon := true, - _.placeholder := "Start typing country name", - _ => children <-- suggestions, - _.events.onInput.mapToValue --> filterValueBus.writer - ) - } - ), - DemoPanel( - "Input with Value State", + Input( + _.showSuggestions := true, + _.showClearIcon := true, + _.placeholder := "Start typing country name", + _ => children <-- suggestions, + _.events.onInput.mapToValue --> filterValueBus.writer + ) + //-- End + }, + DemoPanel("Input with Value State")( + //-- Begin: Input with Value State div(ValueState.allValues.map(state => Input(_.value := state.value, _.valueState := state))) + //-- End ), - DemoPanel( - "Input with Suggestions and Value State message", + DemoPanel("Input with Suggestions and Value State message")( + //-- Begin: Input with Suggestions and Value State message div( Input( _.placeholder := "Choose content density", _.showSuggestions := true, _.slots.valueStateMessage := div("This is an error message. Extra long text used as an error message."), - _ => List("Cozy", "Compact", "Condensed").map(item => UList.Li(_ => item)) + _ => List("Cozy", "Compact", "Condensed").map(item => UList.item(_ => item)) ) ) + //-- End ), - DemoPanel( - "Input as Search Field", { - val searchCriteriaVar: Var[String] = Var("") + DemoPanel("Input as Search Field") { + //-- Begin: Input as Search Field + val searchCriteriaVar: Var[String] = Var("") - val showSearchResultBus: EventBus[Unit] = new EventBus - val closeSearchResultBus: EventBus[Unit] = new EventBus + val showSearchResultBus: EventBus[Unit] = new EventBus + val closeSearchResultBus: EventBus[Unit] = new EventBus - div( - Input( - _.placeholder := "Enter search criteria", - _ => width := "100%", - _.events.onInput.mapToValue --> searchCriteriaVar.writer, - _.slots.icon := Icon(_.name := IconName.search, _ => onClick.mapTo(()) --> showSearchResultBus.writer) + div( + Input( + _.placeholder := "Enter search criteria", + _ => width := "100%", + _.events.onInput.mapToValue --> searchCriteriaVar.writer, + _.slots.icon := Icon(_.name := IconName.search, _ => onClick.mapTo(()) --> showSearchResultBus.writer) + ), + Dialog( + _.headerText := "Search result", + _ => + child <-- showSearchResultBus.events + .sample(searchCriteriaVar.signal) + .map(searchCriteria => div(s"Here would go the results of search for '$searchCriteria'.")), + _.slots.footer := div( + Bar( + _.slots.endContent := Button(_ => "Close", _.events.onClick.mapTo(()) --> closeSearchResultBus.writer), + _.design := BarDesign.Footer + ) ), - Dialog( - _.headerText := "Search result", - _ => - child <-- showSearchResultBus.events - .sample(searchCriteriaVar.signal) - .map(searchCriteria => div(s"Here would go the results of search for '$searchCriteria'.")), - _.slots.footer := div( - Bar( - _.slots.endContent := Button(_ => "Close", _.events.onClick.mapTo(()) --> closeSearchResultBus.writer), - _.design := BarDesign.Footer + _ => + inContext(el => + showSearchResultBus.events.sample(searchCriteriaVar.signal).filter(_.nonEmpty).mapTo(()) --> Observer(_ => + el.ref.show() ) ), - _ => - inContext(el => - showSearchResultBus.events.sample(searchCriteriaVar.signal).filter(_.nonEmpty).mapTo(()) --> Observer( - _ => el.ref.show() - ) - ), - _ => inContext(el => closeSearchResultBus.events --> Observer(_ => el.ref.close())) - ) + _ => inContext(el => closeSearchResultBus.events --> Observer(_ => el.ref.close())) ) - } - ), - DemoPanel( - "Input with Label", + ) + //-- End + }, + DemoPanel("Input with Label")( + //-- Begin: Input with Label div( className := loginFormClass, styleTagForLoginFormClass, @@ -123,6 +128,7 @@ object InputExample extends Example("Input") { ) ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/LabelExample.scala b/demo/src/main/scala/demo/LabelExample.scala index 48b7db6..bb72db6 100644 --- a/demo/src/main/scala/demo/LabelExample.scala +++ b/demo/src/main/scala/demo/LabelExample.scala @@ -3,17 +3,35 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object LabelExample extends Example("Label") { - def component: HtmlElement = div( - DemoPanel("Basic Label", Label(_ => "Simple Label")), - DemoPanel("Required Label", Label(_ => "Required Label", _.required := true)), - DemoPanel("Required Label With Colon", Label(_ => "Required Label", _.required := true, _.showColon := true)), - DemoPanel("Truncated Label", Label(_ => width := "200px", _ => "Long labels are truncated by default.")), - DemoPanel( - "Label 'for'", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Label")( + //-- Begin: Basic label + Label(_ => "Simple Label") + //-- End + ), + DemoPanel("Required Label")( + //-- Begin: Required Label + Label(_ => "Required Label", _.required := true) + //-- End + ), + DemoPanel("Required Label With Colon")( + //-- Begin: Required Label With Colon + Label(_ => "Required Label", _.required := true, _.showColon := true) + //-- End + ), + DemoPanel("Truncated Label")( + //-- Begin: Truncated Label + Label(_ => width := "200px", _ => "Long labels are truncated by default.") + //-- End + ), + DemoPanel("Label 'for'")( + //-- Begin: Label 'for' div( className := loginFormClass, styleTagForLoginFormClass, @@ -66,6 +84,7 @@ object LabelExample extends Example("Label") { CheckBox(_.id := "myCB") ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/LinkExample.scala b/demo/src/main/scala/demo/LinkExample.scala index 87e8336..1193894 100644 --- a/demo/src/main/scala/demo/LinkExample.scala +++ b/demo/src/main/scala/demo/LinkExample.scala @@ -3,13 +3,15 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object LinkExample extends Example("Link") { - def component: HtmlElement = div( - DemoPanel( - "Different Link Designs", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Different Link Designs")( + //-- Begin: Different Link Designs div( LinkDesign.allValues.map(design => Link( @@ -22,6 +24,7 @@ object LinkExample extends Example("Link") { ), Link(_.href := "https://www.scala-js.org/", _.target := LinkTarget._blank, _.disabled := true, _ => "Disabled") ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/ListExample.scala b/demo/src/main/scala/demo/ListExample.scala index 63eb3a0..e489458 100644 --- a/demo/src/main/scala/demo/ListExample.scala +++ b/demo/src/main/scala/demo/ListExample.scala @@ -3,39 +3,41 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object ListExample extends Example("List") { private val countries = List("Argentina", "Belgium", "Bulgaria", "Canada", "Columbia", "Croatia", "Denmark") - def component: HtmlElement = div( - DemoPanel( - "Basic List", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic List")( + //-- Begin: Basic List UList( _ => width := "100%", - _.Li( + _.item( _.icon := IconName.`nutrition-activity`, _.description := "Tropical plant with an edible fruit", _.additionalText := "In-stock", _.additionalTextState := ValueState.Success, _ => "Pineapple" ), - _.Li( + _.item( _.icon := IconName.`nutrition-activity`, _.description := "Occurs between red and yellow", _.additionalText := "Expires", _.additionalTextState := ValueState.Warning, _ => "Orange" ), - _.Li( + _.item( _.icon := IconName.`nutrition-activity`, _.description := "The yellow lengthy fruit", _.additionalText := "Re-stock", _.additionalTextState := ValueState.Information, _ => "Blueberry" ), - _.Li( + _.item( _.icon := IconName.`nutrition-activity`, _.description := "The tropical stone fruit", _.additionalText := "Re-stock", @@ -43,132 +45,139 @@ object ListExample extends Example("List") { _ => "Mango" ) ) + //-- End ), - DemoPanel( - "List with growing='Scroll'", { - val fruits = LazyList.from(0).map { fruitIndex => - val additionalTextState = ValueState.allValues(fruitIndex % ValueState.allValues.size) - UList.Li( - _.icon := IconName.`nutrition-activity`, - _.description := s"This is the description of fruit $fruitIndex", - _.additionalText := additionalTextState.value, - _.additionalTextState := additionalTextState, - _ => s"Fruit $fruitIndex" - ) - } - - val listGrowingBus: EventBus[Unit] = new EventBus - val numberOfFruitsToDisplaySignal = listGrowingBus.events.delay(200).mapTo(5).foldLeft(5)(_ + _) - - val fruitsToDisplaySignal = numberOfFruitsToDisplaySignal.map(fruits.take) - - UList( - _.events.onLoadMore.mapTo(()) --> listGrowingBus.writer, - _ => children <-- fruitsToDisplaySignal, - _ => height := "300px", - _.growing := ListGrowingMode.Scroll, - _.headerText <-- numberOfFruitsToDisplaySignal.map(numberOfFruits => - s"List of fruits (currently $numberOfFruits displayed)." - ) + DemoPanel("List with growing='Scroll'") { + //-- Begin: List with growing='Scroll' + val fruits = LazyList.from(0).map { fruitIndex => + val additionalTextState = ValueState.allValues(fruitIndex % ValueState.allValues.size) + UList.item( + _.icon := IconName.`nutrition-activity`, + _.description := s"This is the description of fruit $fruitIndex", + _.additionalText := additionalTextState.value, + _.additionalTextState := additionalTextState, + _ => s"Fruit $fruitIndex" ) } - ), - DemoPanel( - "List in Single-selection Mode", { - val maybeSelectedCountryVar: Var[Option[String]] = Var(Option.empty) - UList( - _.mode := ListMode.SingleSelect, - _.headerText <-- maybeSelectedCountryVar.signal.map(maybeCountry => - s"Select a country:${maybeCountry.fold("")(country => s" (selected: $country)")}" - ), - _.events.onSelectionChange - .map(_.detail.maybeSelectedItem.flatMap(_.dataset.get("countryName"))) --> maybeSelectedCountryVar.writer, - _ => - countries.map { country => - val isInactive = country == countries.last - UList.Li( - _ => country ++ (if isInactive then " (Item with 'type' set to 'Inactive')" else ""), - _ => dataAttr("country-name") := country, - _.tpe.maybe(Option.when(isInactive)(ListItemType.Inactive)) - ) - } - ) - } - ), - DemoPanel( - "List in Multi-selection Mode", { - val selectedItemsVar: Var[List[String]] = Var(List.empty) - val selectedItemsInfoSignal = selectedItemsVar.signal.map { - case Nil => "No selected items" - case items => s"Selected items: ${items.mkString(", ")}" - } + val listGrowingBus: EventBus[Unit] = new EventBus + val numberOfFruitsToDisplaySignal = listGrowingBus.events.delay(200).mapTo(5).foldLeft(5)(_ + _) - UList( - _.mode := ListMode.MultiSelect, - _.headerText <-- selectedItemsInfoSignal.map(selectedItems => - s"Multiple selection is possible: ($selectedItems)" - ), - _ => countries.map(country => UList.Li(_ => country, _ => dataAttr("country-name") := country)), - _.events.onSelectionChange.map( - _.detail.selectedItems.flatMap(_.dataset.get("countryName")) - ) --> selectedItemsVar.writer - ) + val fruitsToDisplaySignal = numberOfFruitsToDisplaySignal.map(fruits.take) - } - ), - DemoPanel("Buzy List", UList(_.busy := true, _.headerText := "Fetching data...")), - DemoPanel( - "List with GroupHeaders", { - def expansionListItem(expansion: String) = (_: UList.type).Li( - _.iconEnd := true, - _.icon := IconName.`slim-arrow-right`, - _ => expansion - ) - UList( - _.mode := ListMode.MultiSelect, - _.headerText := "Expansion list", - _.group(_ => "Mirrodin Block"), - expansionListItem("Mirrodin"), - expansionListItem("Darksteel"), - expansionListItem("Fifth Dawn"), - _.group(_ => "Kamigawa Block"), - expansionListItem("Champions of Kamigawa"), - expansionListItem("Betrayers of Kamigawa"), - expansionListItem("Saviors of Kamigawa"), - _.group(_ => "Ravnica Block"), - expansionListItem("Ravnica: City of Guilds"), - expansionListItem("Guildpact"), - expansionListItem("Dissension") + UList( + _.events.onLoadMore.mapTo(()) --> listGrowingBus.writer, + _ => children <-- fruitsToDisplaySignal, + _ => height := "300px", + _.growing := ListGrowingMode.Scroll, + _.headerText <-- numberOfFruitsToDisplaySignal.map(numberOfFruits => + s"List of fruits (currently $numberOfFruits displayed)." ) + ) + //-- End + }, + DemoPanel("List in Single-selection Mode") { + //-- Begin: List in Single-selection Mode + val maybeSelectedCountryVar: Var[Option[String]] = Var(Option.empty) + + UList( + _.mode := ListMode.SingleSelect, + _.headerText <-- maybeSelectedCountryVar.signal.map(maybeCountry => + s"Select a country:${maybeCountry.fold("")(country => s" (selected: $country)")}" + ), + _.events.onSelectionChange + .map(_.detail.maybeSelectedItem.flatMap(_.dataset.get("countryName"))) --> maybeSelectedCountryVar.writer, + _ => + countries.map { country => + val isInactive = country == countries.last + UList.item( + _ => country ++ (if isInactive then " (Item with 'type' set to 'Inactive')" else ""), + _ => dataAttr("country-name") := country, + _.tpe.maybe(Option.when(isInactive)(ListItemType.Inactive)) + ) + } + ) + //-- End + }, + DemoPanel("List in Multi-selection Mode") { + //-- Begin: List in Multi-selection Mode + val selectedItemsVar: Var[List[String]] = Var(List.empty) + val selectedItemsInfoSignal = selectedItemsVar.signal.map { + case Nil => "No selected items" + case items => s"Selected items: ${items.mkString(", ")}" } + + UList( + _.mode := ListMode.MultiSelect, + _.headerText <-- selectedItemsInfoSignal.map(selectedItems => + s"Multiple selection is possible: ($selectedItems)" + ), + _ => countries.map(country => UList.item(_ => country, _ => dataAttr("country-name") := country)), + _.events.onSelectionChange.map( + _.detail.selectedItems.flatMap(_.dataset.get("countryName")) + ) --> selectedItemsVar.writer + ) + //-- End + }, + DemoPanel("Buzy List")( + //-- Begin: Buzy List + UList(_.busy := true, _.headerText := "Fetching data...") + //-- End ), - DemoPanel( - "List in Delete Mode", + DemoPanel("List with GroupHeaders") { + //-- Begin: List with GroupHeaders + def expansionListItem(expansion: String) = (_: UList.type).item( + _.iconEnd := true, + _.icon := IconName.`slim-arrow-right`, + _ => expansion + ) + UList( + _.mode := ListMode.MultiSelect, + _.headerText := "Expansion list", + _.group(_ => "Mirrodin Block"), + expansionListItem("Mirrodin"), + expansionListItem("Darksteel"), + expansionListItem("Fifth Dawn"), + _.group(_ => "Kamigawa Block"), + expansionListItem("Champions of Kamigawa"), + expansionListItem("Betrayers of Kamigawa"), + expansionListItem("Saviors of Kamigawa"), + _.group(_ => "Ravnica Block"), + expansionListItem("Ravnica: City of Guilds"), + expansionListItem("Guildpact"), + expansionListItem("Dissension") + ) + //-- End + }, + DemoPanel("List in Delete Mode")( + //-- Begin: List in Delete Mode UList( _.mode := ListMode.Delete, _.headerText := "Note: The list items removal is up to application developers", - _ => countries.map(country => UList.Li(_ => country)) + _ => countries.map(country => UList.item(_ => country)) ) + //-- End ), - DemoPanel( - "List with No Data", + DemoPanel("List with No Data")( + //-- Begin: List with No Data UList(_.headerText := "Products", _.noDataText := "No Data Available", _.separators := ListSeparator.None) + //-- End ), - DemoPanel( - "List Item Speration Types", + DemoPanel("List Item Speration Types")( + //-- Begin: List Item Speration Types div( UList( _.headerText := "No separators", _.separators := ListSeparator.None, - _ => countries.take(3).map(country => UList.Li(_ => country, _.icon := IconName.world)) + _ => countries.take(3).map(country => UList.item(_ => country, _.icon := IconName.world)) ), UList( _.headerText := "Inner separators", _.separators := ListSeparator.Inner, - _ => countries.drop(3).take(3).map(country => UList.Li(_ => country, _.icon := IconName.`hello-world`)) + _ => countries.drop(3).take(3).map(country => UList.item(_ => country, _.icon := IconName.`hello-world`)) ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/MediaGalleryExample.scala b/demo/src/main/scala/demo/MediaGalleryExample.scala index 849ea10..0b20315 100644 --- a/demo/src/main/scala/demo/MediaGalleryExample.scala +++ b/demo/src/main/scala/demo/MediaGalleryExample.scala @@ -3,10 +3,50 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object MediaGalleryExample extends Example("MediaGallery") { - def component: HtmlElement = missing + //-- Begin Common + def fiveMagicWallpapers = List( + "https://media.magic.wizards.com/images/wallpaper/senseis-divining-top-2x2-background-1280x960.jpg", + "https://media.magic.wizards.com/images/wallpaper/mana-vault-2x2-background-1280x960.jpg", + "https://media.magic.wizards.com/images/wallpaper/sparas_headquarters_kieran_yanner_1280x960_poozxbqpcw.jpg", + "https://media.magic.wizards.com/images/wallpaper/baldurs-gate-clb-background-1280x960.jpg", + "https://media.magic.wizards.com/images/wallpaper/1280x960-neo-ukiyo-e-plains.jpg" + ).map(link => MediaGallery.item(_ => img(src := link))) + //-- End Common + + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Usage") { + //-- Begin: Usage + div(width := "800px", MediaGallery(_.showAllThumbnails := true, _ => fiveMagicWallpapers)) + //-- End + }, + DemoPanel("MediaGallery with vertical layout") { + //-- Begin: MediaGallery with vertical layout + div( + width := "800px", + MediaGallery(_.layout := MediaGalleryLayout.Vertical, _.showAllThumbnails := true, _ => fiveMagicWallpapers) + ) + //-- End + }, + DemoPanel("MediaGallery with thumbnails on the right") { + //-- Begin: MediaGallery with thumbnails on the right + div( + width := "800px", + MediaGallery( + _.layout := MediaGalleryLayout.Horizontal, + _.menuHorizontalAlign := MediaGalleryMenuHorizontalAlign.Right, + _.showAllThumbnails := true, + _ => fiveMagicWallpapers + ) + ) + //-- End + }, + mtgImageWarning + ) } diff --git a/demo/src/main/scala/demo/MenuExample.scala b/demo/src/main/scala/demo/MenuExample.scala index 20fe4fa..fd2d257 100644 --- a/demo/src/main/scala/demo/MenuExample.scala +++ b/demo/src/main/scala/demo/MenuExample.scala @@ -3,73 +3,75 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} import org.scalajs.dom.HTMLElement object MenuExample extends Example("Menu") { - def component: HtmlElement = div( - DemoPanel( - "Basic Menu", { - // feed the bus to open the menu at the fed element - val openMenuBus: EventBus[HTMLElement] = new EventBus + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Menu") { + //-- Begin: Basic Menu + // feed the bus to open the menu at the fed element + val openMenuBus: EventBus[HTMLElement] = new EventBus - div( - Button(_ => "Open Menu", _.events.onClick.map(_.target) --> openMenuBus.writer), - Menu( - _ => inContext(el => openMenuBus.events.map(el.ref -> _) --> Observer[(Menu.Ref, HTMLElement)](_ showAt _)), - _.item(_.text := "New File", _.icon := IconName.`add-document`), - _.item(_.text := "New Folder", _.icon := IconName.`add-folder`, _.disabled := true), - _.item(_.text := "Open", _.icon := IconName.`open-folder`, _.startsSection := true), - _.item(_.text := "Close"), - _.item(_.text := "Preferences", _.icon := IconName.`action-settings`, _.startsSection := true), - _.item(_.text := "Exit", _.icon := IconName.`journey-arrive`) - ) + div( + Button(_ => "Open Menu", _.events.onClick.map(_.target) --> openMenuBus.writer), + Menu( + _ => inContext(el => openMenuBus.events.map(el.ref -> _) --> Observer[(Menu.Ref, HTMLElement)](_ showAt _)), + _.item(_.text := "New File", _.icon := IconName.`add-document`), + _.item(_.text := "New Folder", _.icon := IconName.`add-folder`, _.disabled := true), + _.item(_.text := "Open", _.icon := IconName.`open-folder`, _.startsSection := true), + _.item(_.text := "Close"), + _.item(_.text := "Preferences", _.icon := IconName.`action-settings`, _.startsSection := true), + _.item(_.text := "Exit", _.icon := IconName.`journey-arrive`) ) - } - ), - DemoPanel( - "Menu with Sub-menu items", { - // feed the bus to open the menu at the fed element - val openMenuBus: EventBus[HTMLElement] = new EventBus + ) + //-- End + }, + DemoPanel("Menu with Sub-menu items") { + //-- Begin: Menu with Sub-menu items + // feed the bus to open the menu at the fed element + val openMenuBus: EventBus[HTMLElement] = new EventBus - div( - Button(_ => "Open Menu", _.events.onClick.map(_.target) --> openMenuBus.writer), - Menu( - _ => inContext(el => openMenuBus.events.map(el.ref -> _) --> Observer[(Menu.Ref, HTMLElement)](_ showAt _)), - _.item(_.text := "New File", _.icon := IconName.`add-document`), - _.item(_.text := "New Folder", _.icon := IconName.`add-folder`, _.disabled := true), + div( + Button(_ => "Open Menu", _.events.onClick.map(_.target) --> openMenuBus.writer), + Menu( + _ => inContext(el => openMenuBus.events.map(el.ref -> _) --> Observer[(Menu.Ref, HTMLElement)](_ showAt _)), + _.item(_.text := "New File", _.icon := IconName.`add-document`), + _.item(_.text := "New Folder", _.icon := IconName.`add-folder`, _.disabled := true), + _.item( + _.text := "Open", + _.icon := IconName.`open-folder`, + _.startsSection := true, _.item( - _.text := "Open", + _.text := "Open Locally", _.icon := IconName.`open-folder`, - _.startsSection := true, - _.item( - _.text := "Open Locally", - _.icon := IconName.`open-folder`, - _.item(_.text := "Open from C"), - _.item(_.text := "Open from D"), - _.item(_.text := "Open from E") - ), - _.item(_.text := "Open from Cloud") + _.item(_.text := "Open from C"), + _.item(_.text := "Open from D"), + _.item(_.text := "Open from E") ), + _.item(_.text := "Open from Cloud") + ), + _.item( + _.text := "Save", + _.icon := IconName.save, _.item( - _.text := "Save", + _.text := "Save Locally", _.icon := IconName.save, - _.item( - _.text := "Save Locally", - _.icon := IconName.save, - _.item(_.text := "Save from C", _.icon := IconName.save), - _.item(_.text := "Save from D", _.icon := IconName.save), - _.item(_.text := "Save from E", _.icon := IconName.save) - ) - ), - _.item(_.text := "Close"), - _.item(_.text := "Preferences", _.icon := IconName.`action-settings`, _.startsSection := true), - _.item(_.text := "Exit", _.icon := IconName.`journey-arrive`) - ) + _.item(_.text := "Save from C", _.icon := IconName.save), + _.item(_.text := "Save from D", _.icon := IconName.save), + _.item(_.text := "Save from E", _.icon := IconName.save) + ) + ), + _.item(_.text := "Close"), + _.item(_.text := "Preferences", _.icon := IconName.`action-settings`, _.startsSection := true), + _.item(_.text := "Exit", _.icon := IconName.`journey-arrive`) ) - } - ) + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/MessageStripExample.scala b/demo/src/main/scala/demo/MessageStripExample.scala index 37d93b2..60f4d57 100644 --- a/demo/src/main/scala/demo/MessageStripExample.scala +++ b/demo/src/main/scala/demo/MessageStripExample.scala @@ -3,14 +3,16 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} import org.scalajs.dom.HTMLElement object MessageStripExample extends Example("MessageStrip") { - def component: HtmlElement = div( - DemoPanel( - "MessageStrip", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("MessageStrip")( + //-- Begin: MessageStrip div( MessageStripDesign.allValues.map { design => val closeBus: EventBus[Unit] = new EventBus @@ -27,9 +29,10 @@ object MessageStripExample extends Example("MessageStrip") { ) } ) + //-- End ), - DemoPanel( - "MessageStrip With No Close Button", + DemoPanel("MessageStrip With No Close Button")( + //-- Begin: MessageStrip With No Close Button div( MessageStripDesign.allValues.map(design => MessageStrip( @@ -39,9 +42,10 @@ object MessageStripExample extends Example("MessageStrip") { ) ) ) + //-- End ), - DemoPanel( - "MessageStrip With No Icon", + DemoPanel("MessageStrip With No Icon")( + //-- Begin: MessageStrip With No Icon div( MessageStripDesign.allValues.map { design => val closeBus: EventBus[Unit] = new EventBus @@ -59,25 +63,26 @@ object MessageStripExample extends Example("MessageStrip") { ) } ) + //-- End ), - DemoPanel( - "Dynamic Message Strip Generator", { - val clickedBus: EventBus[Unit] = new EventBus - val numberOfClicks = clickedBus.events.mapTo(1).foldLeft(0)(_ + _).changes - div( - Button(_ => "Generate MessageStrip", _.events.onClick.mapTo(()) --> clickedBus.writer), - child <-- numberOfClicks.map(count => - MessageStrip( - _.design := MessageStripDesign.allValues(count % MessageStripDesign.allValues.size), - _ => s"You clicked $count times.", - _.hideCloseButton := true - ) + DemoPanel("Dynamic Message Strip Generator") { + //-- Begin: Dynamic Message Strip Generator + val clickedBus: EventBus[Unit] = new EventBus + val numberOfClicks = clickedBus.events.mapTo(1).foldLeft(0)(_ + _).changes + div( + Button(_ => "Generate MessageStrip", _.events.onClick.mapTo(()) --> clickedBus.writer), + child <-- numberOfClicks.map(count => + MessageStrip( + _.design := MessageStripDesign.allValues(count % MessageStripDesign.allValues.size), + _ => s"You clicked $count times.", + _.hideCloseButton := true ) ) - } - ), - DemoPanel( - "Custom MessageStrip", + ) + //-- End + }, + DemoPanel("Custom MessageStrip")( + //-- Begin: Custom MessageStrip div( MessageStrip( _.design := MessageStripDesign.Information, @@ -116,6 +121,7 @@ object MessageStripExample extends Example("MessageStrip") { ) ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/MultiComboBoxExample.scala b/demo/src/main/scala/demo/MultiComboBoxExample.scala index 75497b7..1d41b79 100644 --- a/demo/src/main/scala/demo/MultiComboBoxExample.scala +++ b/demo/src/main/scala/demo/MultiComboBoxExample.scala @@ -3,23 +3,26 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object MultiComboBoxExample extends Example("MultiComboBox") { private val countries = List("Argentina", "Belgium", "Bulgaria", "Canada", "Columbia", "Croatia", "Denmark") - def component: HtmlElement = div( - DemoPanel( - "Basic MultiComboBox", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic MultiComboBox")( + //-- Begin: Basic MultiComboBox" div( MultiComboBox(_.placeholder := "Type your value", _.item(_.selected := true, _.text := "UI5")), MultiComboBox(_.readonly := true, _.value := "Readonly combo", _.item(_.selected := true, _.text := "UI5")), MultiComboBox(_.disabled := true, _.value := "Disabled combo", _.item(_.selected := true, _.text := "UI5")) ) + //-- End ), - DemoPanel( - "MultiComboBox with items", + DemoPanel("MultiComboBox with items")( + //-- Begin: MultiComboBox with items MultiComboBox( _.placeholder := "Choose your countries", _ => width := "500px", @@ -28,9 +31,10 @@ object MultiComboBoxExample extends Example("MultiComboBox") { MultiComboBox.item(_.text := country, _.selected := (index == 0)) ) ) + //-- End ), - DemoPanel( - "MultiComboBox with free text input", + DemoPanel("MultiComboBox with free text input")( + //-- Begin: MultiComboBox with free text input MultiComboBox( _.placeholder := "Choose your countries", _ => width := "500px", @@ -40,9 +44,10 @@ object MultiComboBoxExample extends Example("MultiComboBox") { MultiComboBox.item(_.text := country, _.selected := (index % 3 == 0)) ) ) + //-- End ), - DemoPanel( - "MultiComboBox with Value State", + DemoPanel("MultiComboBox with Value State")( + //-- Begin: MultiComboBox with Value State div( ValueState.allValues.filterNot(_ == ValueState.Information).filterNot(_ == ValueState.None).zipWithIndex.map { (valueState, index) => @@ -52,7 +57,28 @@ object MultiComboBoxExample extends Example("MultiComboBox") { ) } ) - ) + //-- End + ), + DemoPanel("MultiComboBox with Grouping of Items") { + //-- Begin: MultiComboBox with Grouping of Items + MultiComboBox( + _.placeholder := "Select a country", + _.group(_.text := "Asia"), + _.item(_.text := "Afghanistan"), + _.item(_.text := "China"), + _.item(_.text := "India"), + _.item(_.text := "Indonesia"), + _.group(_.text := "Europe"), + _.item(_.text := "Austria"), + _.item(_.text := "Belgium"), + _.item(_.text := "Germany"), + _.item(_.text := "Italy"), + _.group(_.text := "North America"), + _.item(_.text := "Canada"), + _.item(_.text := "United States") + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/MultiInputExample.scala b/demo/src/main/scala/demo/MultiInputExample.scala index 87021a7..d5b836e 100644 --- a/demo/src/main/scala/demo/MultiInputExample.scala +++ b/demo/src/main/scala/demo/MultiInputExample.scala @@ -3,10 +3,106 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object MultiInputExample extends Example("MultiInput") { - def component: HtmlElement = missing + //-- Begin Common + val countries = List("Argentina", "Belgium", "Bulgaria", "Canada", "Columbia", "Croatia", "Denmark") + //-- End Common + + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + styleTagForLoginFormClass, + DemoPanel("Basic Multi Input") { + //-- Begin: Basic Multi Input + val firstValueVar = Var("basic input") + val secondValueVar = Var("value help icon") + div( + display := "flex", + className := loginFormClass, + div( + Label( + _.wrappingType := WrappingType.Normal, + _ => width := "200px", + _ => "MultiInput", + _ => child.text <-- firstValueVar.signal.map(value => s" (current value is $value)") + ), + MultiInput( + _.value <-- firstValueVar, + _.events.onInput.mapToValue --> firstValueVar.writer, + _.events.onChange.mapToValue --> firstValueVar.writer + ) + ), + div( + Label( + _.wrappingType := WrappingType.Normal, + _ => width := "200px", + _ => "MultiInput", + _ => child.text <-- secondValueVar.signal.map(value => s" (current value is $value)") + ), + MultiInput( + _.showValueHelpIcon := true, + _.value <-- secondValueVar, + _.events.onInput.mapToValue --> secondValueVar.writer, + _.events.onChange.mapToValue --> secondValueVar.writer + ) + ) + ) + //-- End + }, + DemoPanel("Multi Input with tokens") { + //-- Begin: Multi Input with tokens + div( + div( + MultiInput(_.slots.tokens := MultiInput.token(_.text := "Bulgaria")), + MultiInput(_.slots.tokens := countries.map(country => MultiInput.token(_.text := country))) + ), + MessageStrip( + _.design := MessageStripDesign.Information, + _ => "These input are only there for display. They won't add tokens dynamically. See below for such example." + ) + ) + //-- End + }, + DemoPanel("Multi Input and token creation onChange") { + //-- Begin: Multi Input and token creation onChange + val tokenValuesVar = Var(List("Argentina")) + + val changeBus: EventBus[String] = new EventBus + + // Emits the new values list, with whether or not they should be actually patched to the values + // This check is required because we want tokens to be unique + val newValuesWithShouldWeUpdate = changeBus.events + .withCurrentValueOf(tokenValuesVar.signal) + .map((newValue, previousValues) => (previousValues :+ newValue, !previousValues.contains(newValue))) + + // emits when token must be changed + val newValuesChanges = newValuesWithShouldWeUpdate.collect { case (values, true) => values } + + // When the new value was already present, we issue the error message ... + val valueStateBecomesErrorEvents = newValuesWithShouldWeUpdate.filter(!_._2).mapTo(ValueState.Error) + // ... and we clear it 2 seconds later + val valueStateBecomesNormalEvents = valueStateBecomesErrorEvents.delay(2000).mapTo(ValueState.None) + + val valueStateChanges = EventStream.merge(valueStateBecomesErrorEvents, valueStateBecomesNormalEvents) + + MultiInput( + _.showSuggestions := true, + _.valueState <-- valueStateChanges, + _ => width := "50%", + _.slots.valueStateMessage := div("Token is already in the list"), + _ => countries.map(country => MultiInput.suggestion(_.text := country)), + _.slots.tokens <-- tokenValuesVar.signal.map(_.map(tokenValue => MultiInput.token(_.text := tokenValue))), + _.events.onChange.map(_.target.value) --> changeBus.writer, + _ => newValuesChanges --> tokenValuesVar.writer, + _.events.onTokenDelete.map(_.detail.token.text) --> tokenValuesVar.updater((values, toRemove) => + values.filterNot(_ == toRemove) + ) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/NotificationListGroupItemExample.scala b/demo/src/main/scala/demo/NotificationListGroupItemExample.scala index 9af6f34..b37f7af 100644 --- a/demo/src/main/scala/demo/NotificationListGroupItemExample.scala +++ b/demo/src/main/scala/demo/NotificationListGroupItemExample.scala @@ -3,10 +3,52 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object NotificationListGroupItemExample extends Example("NotificationListGroupItem") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("NotificationListGroupItem") { + //-- Begin: NotificationListGroupItem + UList( + _.headerText := "Notifications grouped", + _.notificationGroup( + _.showClose := true, + _.showCounter := true, + _.priority := Priority.High, + _.titleText := "Some high priority notifications", + _.item( + _.showClose := true, + _.titleText := "Some notification was triggered!", + _.priority := Priority.High + ), + _.item( + _.showClose := true, + _.titleText := "Some other notification was triggered!", + _.priority := Priority.High + ) + ), + _.notificationGroup( + _.showClose := true, + _.showCounter := true, + _.priority := Priority.Medium, + _.titleText := "Some medium priority notifications", + _.item( + _.showClose := true, + _.titleText := "Some medium notification was triggered!", + _.priority := Priority.Medium + ), + _.item( + _.showClose := true, + _.titleText := "Some other medium notification was triggered!", + _.priority := Priority.Medium + ) + ) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/NotificationListItemExample.scala b/demo/src/main/scala/demo/NotificationListItemExample.scala index 0dc297a..2355abe 100644 --- a/demo/src/main/scala/demo/NotificationListItemExample.scala +++ b/demo/src/main/scala/demo/NotificationListItemExample.scala @@ -3,10 +3,116 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} +import org.scalajs.dom object NotificationListItemExample extends Example("NotificationListItem") { - def component: HtmlElement = missing + // I know it's a bit sketchy to put the below thing common, but in this case it's ok, just need to make sure that + // the ids do not clash accross examples... + //-- Begin Common + // Each notification receives an id in its dataset, that is used to be removed when the delete icon is clicked + val closeItemsBus: EventBus[dom.HTMLElement] = new EventBus + + val notifIdName = "notif-id" + // Modifiers to be given to the notification with specified id. It will hide the item on delete + def notifWithId(id: String): NotificationListItem.ModFunction = _ => + List( + hidden <-- closeItemsBus.events.map(_.dataset("notifId")).filter(_ == id).mapTo(true).startWith(false), + dataAttr(notifIdName) := id + ) + //-- End Common + + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("NotificationListItem") { + //-- Begin: NotificationListItem + // Each notification receives an id in its dataset, that is used to be removed when the delete icon is clicked + val closeItemsBus: EventBus[dom.HTMLElement] = new EventBus + + val notifIdName = "notif-id" + // Modifiers to be given to the notification with specified id. It will hide the item on delete + def notifWithId(id: String): NotificationListItem.ModFunction = _ => + List( + hidden <-- closeItemsBus.events.map(_.dataset("notifId")).filter(_ == id).mapTo(true).startWith(false), + dataAttr(notifIdName) := id + ) + + UList( + _.headerText := "Notifications", + _.events.onItemClose.map(_.detail.item) --> closeItemsBus.writer, + _.notificationItem( + notifWithId("notif-1"), + _.showClose := true, + _.titleText := "New order (#2525) With a very long title - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.", + _.priority := Priority.High, + _ => + "And with a very long description and long labels of the action buttons - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.", + _.slots.avatar := Avatar(_.size := AvatarSize.XS, _ => img(src := MTG.manaSymbolsRefs("W"))), + _.slots.footnotes := span("Monique Legrand"), + _.slots.footnotes := span("2 Days") + ), + _.notificationItem( + notifWithId("notif-2"), + _.showClose := true, + _.titleText := "New order (#2526) With a very long title - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.", + _.priority := Priority.High, + _ => + "And with a very long description and long labels of the action buttons - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.", + _.slots.avatar := Avatar(_.size := AvatarSize.XS, _ => img(src := MTG.manaSymbolsRefs("B"))), + _.slots.footnotes := span("Alain Chevalier"), + _.slots.footnotes := span("2 Days") + ) + ) + //-- End + }, + DemoPanel("NotificationListItem In ShellBar") { + //-- Begin: NotificationListItem In ShellBar + val openNotifPopoverBus: EventBus[dom.HTMLElement] = new EventBus + div( + ShellBar( + _.primaryTitle := "Corporate Portal", + _.slots.logo := img(src := "/images/avatars/scala-logo.png"), + _.showNotifications := true, + _.notificationsCount := "4", + _.events.onNotificationsClick.map(_.detail.targetRef) --> openNotifPopoverBus.writer + ), + Popover( + _ => inContext(el => openNotifPopoverBus.events.map(el.ref -> _) --> Popover.showAtObserver), + _ => maxWidth := "600px", + _.placementType := PopoverPlacementType.Bottom, + _.horizontalAlign := PopoverHorizontalAlign.Right, + _ => + UList( + _.headerText := "Notifications", + _.events.onItemClose.map(_.detail.item) --> closeItemsBus.writer, + _.notificationItem( + notifWithId("notif-shellbar-1"), + _.showClose := true, + _.titleText := "New order (#2525) With a very long title - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.", + _.priority := Priority.High, + _ => "And with a very long description and long labels of the action buttons - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.", + _.slots.avatar := Avatar(_.size := AvatarSize.XS, _ => img(src := MTG.manaSymbolsRefs("W"))), + _.slots.footnotes := span("Monique Legrand"), + _.slots.footnotes := span("2 Days"), + _.slots.actions := NotificationListItem.action(_.icon := IconName.accept, _.text := "Accept") + ), + _.notificationItem( + notifWithId("notif-shellbar-2"), + _.showClose := true, + _.titleText := "New order (#2526) With a very long title - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.", + _.priority := Priority.High, + _ => "And with a very long description and long labels of the action buttons - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent feugiat, turpis vel scelerisque pharetra, tellus odio vehicula dolor, nec elementum lectus turpis at nunc.", + _.slots.avatar := Avatar(_.size := AvatarSize.XS, _ => img(src := MTG.manaSymbolsRefs("B"))), + _.slots.footnotes := span("Alain Chevalier"), + _.slots.footnotes := span("2 Days") + ) + ) + ) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/PageExample.scala b/demo/src/main/scala/demo/PageExample.scala index 83b23cc..f259305 100644 --- a/demo/src/main/scala/demo/PageExample.scala +++ b/demo/src/main/scala/demo/PageExample.scala @@ -3,10 +3,51 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object PageExample extends Example("Page") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Page with floating footer") { + //-- Begin: Page with floating footer + Page( + _.floatingFooter := true, + _ => height := "500px", + _ => width := "500px", + _.slots.header := Bar( + _.design := BarDesign.Header, + _.slots.startContent := Button(_.tooltip := "Go Home", _.icon := IconName.home), + _.slots.endContent := Button(_.tooltip := "Settings", _.icon := IconName.`action-settings`), + _ => "Page Title" + ), + _ => + div( + overflowY := "auto", + p( + "103.3. Each player begins the game with a starting life total of 20. Some variant games have different starting life totals." + ), + p("103.3a In a Two-Headed Giant game, each team’s starting life total is 30."), + p( + "103.3b In a Vanguard game, each player’s starting life total is 20 plus or minus the life modifier of their vanguard card." + ), + p("103.3c In a Commander game, each player’s starting life total is 40."), + p( + "103.3d In a two-player Brawl game, each player’s starting life total is 25. In a multiplayer Brawl game, each player’s starting life total is 30." + ), + p("103.3e In an Archenemy game, the archenemy’s starting life total is 40.") + ), + _.slots.footer := Bar( + _.design := BarDesign.FloatingFooter, + _.slots.startContent := Button(_.design := ButtonDesign.Transparent, _.icon := IconName.home), + _.slots.endContent := Button(_.design := ButtonDesign.Positive, _ => "Accept"), + _.slots.endContent := Button(_.design := ButtonDesign.Negative, _ => "Reject"), + _.slots.endContent := Button(_.design := ButtonDesign.Transparent, _ => "Cancel") + ) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/PanelExample.scala b/demo/src/main/scala/demo/PanelExample.scala index c016584..b4a4a5f 100644 --- a/demo/src/main/scala/demo/PanelExample.scala +++ b/demo/src/main/scala/demo/PanelExample.scala @@ -3,15 +3,17 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object PanelExample extends Example("Panel") { private val countries = List("Argentina", "Belgium", "Bulgaria", "Canada", "Columbia", "Croatia", "Denmark") - def component: HtmlElement = div( - DemoPanel( - "Basic Panel", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Panel")( + //-- Begin: Basic Panel Panel( _ => width := "100%", _.headerText := "Both expandable and expanded", @@ -25,33 +27,36 @@ object PanelExample extends Example("Panel") { "do eu duis elit. Sunt ea pariatur nulla est laborum proident sunt labore commodo Lorem laboris nisi Lorem." ) ) + //-- End ), - DemoPanel( - "Panel with List", + DemoPanel("Panel with List")( + //-- Begin: Panel with List Panel( _.headerText := "Select your country", _ => width := "100%", _ => UList( _.mode := ListMode.MultiSelect, - _ => countries.map(country => UList.Li(_ => country)) + _ => countries.map(country => UList.item(_ => country)) ) ) + //-- End ), - DemoPanel( - "Fixed Panel (Can't be Collapsed/Expanded)", + DemoPanel("Fixed Panel (Can't be Collapsed/Expanded)")( + //-- Begin: Fixed Panel (Can't be Collapsed/Expanded) Panel( _.fixed := true, _.headerText := "Country Of Birth", _ => UList( _.mode := ListMode.SingleSelectBegin, - _ => countries.map(country => UList.Li(_ => country)) + _ => countries.map(country => UList.item(_ => country)) ) ) + //-- End ), - DemoPanel( - "Panel with Custom Header", + DemoPanel("Panel with Custom Header")( + //-- Begin: Panel with Custom Header div( styleTag(""" |.header { @@ -75,10 +80,11 @@ object PanelExample extends Example("Panel") { _ => UList( _.mode := ListMode.MultiSelect, - _ => countries.map(country => UList.Li(_ => country)) + _ => countries.map(country => UList.item(_ => country)) ) ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/PopoverExample.scala b/demo/src/main/scala/demo/PopoverExample.scala index f1d0880..38b65b8 100644 --- a/demo/src/main/scala/demo/PopoverExample.scala +++ b/demo/src/main/scala/demo/PopoverExample.scala @@ -3,52 +3,49 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} import org.scalajs.dom.HTMLElement object PopoverExample extends Example("Popover") { - def component: HtmlElement = div( - DemoPanel( - "Basic Popover", { - val openPopoverBus: EventBus[Option[HTMLElement]] = new EventBus + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Popover") { + //-- Begin: Basic Popover + val openPopoverBus: EventBus[Option[HTMLElement]] = new EventBus - div( - Button( - _ => "Open Popover", - _.design := ButtonDesign.Emphasized, - _.events.onClick.map(_.target).map(Some(_)) --> openPopoverBus.writer - ), - Popover( - _ => - inContext(el => - openPopoverBus.events --> Observer[Option[HTMLElement]] { - case Some(element) => el.ref.showAt(element) - case None => el.ref.close() - } - ), - _.headerText := "Newsletter subscription", - _ => + div( + Button( + _ => "Open Popover", + _.design := ButtonDesign.Emphasized, + _.events.onClick.map(_.target).map(Some(_)) --> openPopoverBus.writer + ), + Popover( + _.showAtFromEvents(openPopoverBus.events.collect { case Some(opener) => opener }), + _.closeFromEvents(openPopoverBus.events.collect { case None => () }), + _.headerText := "Newsletter subscription", + _ => + div( + className := loginFormClass, + styleTagForLoginFormClass, div( - className := loginFormClass, - styleTagForLoginFormClass, - div( - Label(_.forId := "emailInput", _.required := true, _ => "Email"), - Input(_.id := "emailInput", _.placeholder := "Enter Email", _.tpe := InputType.Email) - ) - ), - _.slots.footer := div( - div(flex := "1"), - Button( - _.design := ButtonDesign.Emphasized, - _ => "Subscribe", - _.events.onClick.mapTo(None) --> openPopoverBus.writer + Label(_.forId := "emailInput", _.required := true, _ => "Email"), + Input(_.id := "emailInput", _.placeholder := "Enter Email", _.tpe := InputType.Email) ) + ), + _.slots.footer := div( + div(flex := "1"), + Button( + _.design := ButtonDesign.Emphasized, + _ => "Subscribe", + _.events.onClick.mapTo(None) --> openPopoverBus.writer ) ) ) - } - ) + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/ProductSwitchExample.scala b/demo/src/main/scala/demo/ProductSwitchExample.scala index 615e995..0987044 100644 --- a/demo/src/main/scala/demo/ProductSwitchExample.scala +++ b/demo/src/main/scala/demo/ProductSwitchExample.scala @@ -3,14 +3,16 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} import org.scalajs.dom.HTMLElement object ProductSwitchExample extends Example("ProductSwitch") { - def component: HtmlElement = div( - DemoPanel( - "Basic sample", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic sample")( + //-- Begin: Basic sample ProductSwitch( _.item(_.titleText := "Home", _.subtitleText := "Central Home", _.icon := IconName.home), _.item( @@ -21,51 +23,52 @@ object ProductSwitchExample extends Example("ProductSwitch") { _.item(_.titleText := "Catalog", _.subtitleText := "Ariba", _.icon := IconName.contacts), _.item(_.titleText := "Travel & Expense", _.subtitleText := "Concur", _.icon := IconName.flight) ) + //-- End ), - DemoPanel( - "ProductSwitch within ShellBar", { - val togglePopoverOpeningEventBus: EventBus[HTMLElement] = new EventBus + DemoPanel("ProductSwitch within ShellBar") { + //-- Begin: ProductSwitch within ShellBar + val togglePopoverOpeningEventBus: EventBus[HTMLElement] = new EventBus - val togglePopoverOpeningEvents = togglePopoverOpeningEventBus.events - .foldLeft(Option.empty[HTMLElement]) { - case (Some(_), _) => None - case (None, element) => Some(element) - } - .changes + val togglePopoverOpeningEvents = togglePopoverOpeningEventBus.events + .foldLeft(Option.empty[HTMLElement]) { + case (Some(_), _) => None + case (None, element) => Some(element) + } + .changes - div( - ShellBar( - _.primaryTitle := "Corporate Portal", - _.secondaryTitle := "secondary title", - _.slots.logo := img(src := "/images/avatars/scala-logo.png"), - _.showProductSwitch := true, - _.showCoPilot := true, - _.events.onProductSwitchClick.map(_.detail.targetRef) --> togglePopoverOpeningEventBus.writer - ), - Popover( - _ => - inContext(el => - togglePopoverOpeningEvents --> Observer[Option[HTMLElement]] { - case Some(element) => el.ref.showAt(element) - case None => el.ref.close() - } + div( + ShellBar( + _.primaryTitle := "Corporate Portal", + _.secondaryTitle := "secondary title", + _.slots.logo := img(src := "/images/avatars/scala-logo.png"), + _.showProductSwitch := true, + _.showCoPilot := true, + _.events.onProductSwitchClick.map(_.detail.targetRef) --> togglePopoverOpeningEventBus.writer + ), + Popover( + _ => + inContext(el => + togglePopoverOpeningEvents --> Observer[Option[HTMLElement]] { + case Some(element) => el.ref.showAt(element) + case None => el.ref.close() + } + ), + _.placementType := PopoverPlacementType.Bottom, + _ => + ProductSwitch( + _.item(_.titleText := "Home", _.subtitleText := "Central Home", _.icon := IconName.home), + _.item( + _.titleText := "Analytics Cloud", + _.subtitleText := "Analystics Could", + _.icon := IconName.`business-objects-experience` ), - _.placementType := PopoverPlacementType.Bottom, - _ => - ProductSwitch( - _.item(_.titleText := "Home", _.subtitleText := "Central Home", _.icon := IconName.home), - _.item( - _.titleText := "Analytics Cloud", - _.subtitleText := "Analystics Could", - _.icon := IconName.`business-objects-experience` - ), - _.item(_.titleText := "Catalog", _.subtitleText := "Ariba", _.icon := IconName.contacts), - _.item(_.titleText := "Travel & Expense", _.subtitleText := "Concur", _.icon := IconName.flight) - ) - ) + _.item(_.titleText := "Catalog", _.subtitleText := "Ariba", _.icon := IconName.contacts), + _.item(_.titleText := "Travel & Expense", _.subtitleText := "Concur", _.icon := IconName.flight) + ) ) - } - ) + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/ProgressIndicatorExample.scala b/demo/src/main/scala/demo/ProgressIndicatorExample.scala index e449703..0ec0a96 100644 --- a/demo/src/main/scala/demo/ProgressIndicatorExample.scala +++ b/demo/src/main/scala/demo/ProgressIndicatorExample.scala @@ -3,41 +3,47 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object ProgressIndicatorExample extends Example("ProgressIndicator") { - def component: HtmlElement = div( - DemoPanel( - "Basic Progress Indicator", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Progress Indicator")( + //-- Begin: Basic Progress Indicator div( ProgressIndicator(_.value := 0), ProgressIndicator(_.value := 25), ProgressIndicator(_.value := 75), ProgressIndicator(_.value := 100) ) + //-- End ), - DemoPanel( - "Progress Indicator With Custom Display Value", + DemoPanel("Progress Indicator With Custom Display Value")( + //-- Begin: Progress Indicator With Custom Display Value ProgressIndicator( _.value := 25, _.displayValue := "Custom Display Value" ) + //-- End ), - DemoPanel( - "Progress Indicator With Value State", + DemoPanel("Progress Indicator With Value State")( + //-- Begin: Progress Indicator With Value State div( ValueState.allValues .zip(10 to 100 by 20) .map((valueState, value) => ProgressIndicator(_.value := value, _.valueState := valueState)) ) + //-- End ), - DemoPanel( - "Progress Indicator With Custom Sizes", + DemoPanel("Progress Indicator With Custom Sizes")( + //-- Begin: Progress Indicator With Custom Sizes div( ProgressIndicator(_.value := 25, _ => height := "50px", _ => width := "200px"), ProgressIndicator(_.value := 75, _ => height := "50px", _ => width := "200px") ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/RadioButtonExample.scala b/demo/src/main/scala/demo/RadioButtonExample.scala index a086678..bb4d79d 100644 --- a/demo/src/main/scala/demo/RadioButtonExample.scala +++ b/demo/src/main/scala/demo/RadioButtonExample.scala @@ -3,50 +3,52 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object RadioButtonExample extends Example("RadioButton") { - def component: HtmlElement = div( - DemoPanel( - "Basic RadioButton Types", { - val texts = LazyList.from(0).map('A' + _).map(_.toChar).map("Option " ++ _.toString) - val name = "GroupA" + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic RadioButton Types") { + //-- Begin: Basic RadioButton Types + val texts = LazyList.from(0).map('A' + _).map(_.toChar).map("Option " ++ _.toString) + val name = "GroupA" - div( - RadioButton(_.text := texts(0), _.checked := true, _.name := name), - RadioButton(_.text := texts(1), _.valueState := ValueState.None, _.name := name), - RadioButton(_.text := texts(2), _.valueState := ValueState.Warning, _.name := name), - RadioButton(_.text := texts(6), _.disabled := true, _.name := name), - RadioButton(_.text := texts(7), _.readonly := true, _.name := name) - ) - } - ), - DemoPanel( - "RadioButton in group - navigate via [UP/Right] and [DOWN/Left] arrow keys", { - val selectedValueVar: Var[String] = Var("None") + div( + RadioButton(_.text := texts(0), _.checked := true, _.name := name), + RadioButton(_.text := texts(1), _.valueState := ValueState.None, _.name := name), + RadioButton(_.text := texts(2), _.valueState := ValueState.Warning, _.name := name), + RadioButton(_.text := texts(6), _.disabled := true, _.name := name), + RadioButton(_.text := texts(7), _.readonly := true, _.name := name) + ) + //-- End + }, + DemoPanel("RadioButton in group - navigate via [UP/Right] and [DOWN/Left] arrow keys") { + //-- Begin: RadioButton in group - navigate via [UP/Right] and [DOWN/Left] arrow keys + val selectedValueVar: Var[String] = Var("None") + div( + h1("Group of states"), + Label(_ => child.text <-- selectedValueVar.signal.map(state => s"Selected radio: $state")), div( - h1("Group of states"), - Label(_ => child.text <-- selectedValueVar.signal.map(state => s"Selected radio: $state")), - div( - display := "flex", - flexDirection := "column", - ValueState.allValues.map(state => - RadioButton( - _.name := "GroupB", - _.text := state.value, - _.events.onChange.mapToChecked.filter(identity).mapTo(state.value) --> selectedValueVar.writer, - _.checked <-- selectedValueVar.signal.map(_ == state.value), - _.valueState := state - ) + display := "flex", + flexDirection := "column", + ValueState.allValues.map(state => + RadioButton( + _.name := "GroupB", + _.text := state.value, + _.events.onChange.mapToChecked.filter(identity).mapTo(state.value) --> selectedValueVar.writer, + _.checked <-- selectedValueVar.signal.map(_ == state.value), + _.valueState := state ) ) ) - } - ), - DemoPanel( - "RadioButton with Text Wrapping", + ) + //-- End + }, + DemoPanel("RadioButton with Text Wrapping")( + //-- Begin: RadioButton with Text Wrapping div( RadioButton( _ => width := "300px", @@ -61,6 +63,7 @@ object RadioButtonExample extends Example("RadioButton") { _.name := "GroupD" ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/RangeSliderExample.scala b/demo/src/main/scala/demo/RangeSliderExample.scala index 167417a..2b3970e 100644 --- a/demo/src/main/scala/demo/RangeSliderExample.scala +++ b/demo/src/main/scala/demo/RangeSliderExample.scala @@ -3,10 +3,62 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object RangeSliderExample extends Example("RangeSlider") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Range Slider") { + //-- Begin: Basic Range Slider + val startValueVar: Var[Double] = Var(0) + val endValueVar: Var[Double] = Var(20) + + div( + Label(_ => + child.text <-- startValueVar.signal + .combineWith(endValueVar.signal) + .map((start, end) => s"Currently selected range: ($start, $end)") + ), + RangeSlider( + _.endValue <-- endValueVar.signal, + _.startValue <-- startValueVar.signal, + _.events.onChange.map(_.target.startValue) --> startValueVar.writer, + _.events.onChange.map(_.target.endValue) --> endValueVar.writer + ) + ) + //-- End + }, + DemoPanel("Range Slider with Custom 'min', 'max', 'startValue' and 'endValue' Properties") { + //-- Begin: Range Slider with Custom 'min', 'max', 'startValue' and 'endValue' Properties + RangeSlider(_.min := 100, _.max := 200, _.startValue := 120, _.endValue := 150) + //-- End + }, + DemoPanel("Range Slider with Tooltips") { + //-- Begin: Range Slider with Tooltips + RangeSlider(_.startValue := 3, _.endValue := 13, _.showTooltip := true) + //-- End + }, + DemoPanel("Range Slider with Tickmarks and Custom Step") { + //-- Begin: Range Slider with Tickmarks and Custom Step + RangeSlider(_.step := 2, _.startValue := 4, _.endValue := 12, _.showTickmarks := true) + //-- End + }, + DemoPanel("Range Slider with Tooltips, Tickmarks and Labels") { + //-- Begin: Range Slider with Tooltips, Tickmarks and Labels + RangeSlider( + _.min := 0, + _.max := 112, + _.step := 2, + _.startValue := 4, + _.endValue := 12, + _.showTooltip := true, + _.labelInterval := 2, + _.showTickmarks := true + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/RatingIndicatorExample.scala b/demo/src/main/scala/demo/RatingIndicatorExample.scala index a94f55e..34786c2 100644 --- a/demo/src/main/scala/demo/RatingIndicatorExample.scala +++ b/demo/src/main/scala/demo/RatingIndicatorExample.scala @@ -3,46 +3,52 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object RatingIndicatorExample extends Example("RatingIndicator") { - def component: HtmlElement = div( + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( styleTag(""" |div > ui5-rating-indicator { | margin-right: 2em; |} |""".stripMargin), - DemoPanel( - "Basic Rating Indicator", + DemoPanel("Basic Rating Indicator")( + //-- Begin: Basic Rating Indicator div( RatingIndicator(_.events.onChange.map(_.target.value) --> Observer(println)), RatingIndicator(_.value := 3), RatingIndicator(_.value := 3.7) ) + //-- End ), - DemoPanel( - "Rating Indicator With Different Max Value", + DemoPanel("Rating Indicator With Different Max Value")( + //-- Begin: Rating Indicator With Different Max Value div( RatingIndicator(_.max := 10, _.value := 5), RatingIndicator(_.max := 3, _.value := 3) ) + //-- End ), - DemoPanel( - "Disabled Rating Indicator", + DemoPanel("Disabled Rating Indicator")( + //-- Begin: Disabled Rating Indicator div( RatingIndicator(_.value := 4, _.disabled := true), RatingIndicator(_.max := 10, _.value := 5, _.disabled := true), RatingIndicator(_.value := 6, _.max := 6, _.disabled := true) ) + //-- End ), - DemoPanel( - "Readonly Rating Indicator", + DemoPanel("Readonly Rating Indicator")( + //-- Begin: Readonly Rating Indicator div( RatingIndicator(_.value := 4, _.readonly := true), RatingIndicator(_.max := 10, _.value := 5, _.readonly := true), RatingIndicator(_.value := 6, _.max := 6, _.readonly := true) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/ResponsivePopoverExample.scala b/demo/src/main/scala/demo/ResponsivePopoverExample.scala index de095ea..6d49226 100644 --- a/demo/src/main/scala/demo/ResponsivePopoverExample.scala +++ b/demo/src/main/scala/demo/ResponsivePopoverExample.scala @@ -3,10 +3,50 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} +import org.scalajs.dom object ResponsivePopoverExample extends Example("ResponsivePopover") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic ResponsivePopover") { + //-- Begin: Basic ResponsivePopover + val openPopoverBus: EventBus[dom.HTMLElement] = new EventBus + val closePopoverBus: EventBus[Unit] = new EventBus + + div( + Button( + _.design := ButtonDesign.Emphasized, + _ => "Open Popover", + _.events.onClick.map(_.target) --> openPopoverBus.writer + ), + ResponsivePopover( + _.showAtFromEvents(openPopoverBus.events), + _.closeFromEvents(closePopoverBus.events), + _.headerText := "Newsletter subscription", + _ => + div( + className := loginFormClass, + styleTagForLoginFormClass, + div( + Label(_.forId := "emailInput", _.required := true, _ => "Email"), + Input(_.id := "emailInput", _.placeholder := "Enter Email", _.tpe := InputType.Email) + ) + ), + _.slots.footer := div( + div(flex := "1"), + Button( + _.design := ButtonDesign.Emphasized, + _ => "Subscribe", + _.events.onClick.mapTo(()) --> closePopoverBus.writer + ) + ) + ) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/SegmentedButtonExample.scala b/demo/src/main/scala/demo/SegmentedButtonExample.scala index ac0e392..c886c30 100644 --- a/demo/src/main/scala/demo/SegmentedButtonExample.scala +++ b/demo/src/main/scala/demo/SegmentedButtonExample.scala @@ -3,30 +3,34 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object SegmentedButtonExample extends Example("SegmentedButton") { - def component: HtmlElement = div( - DemoPanel( - "Basic SegmentedButton", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic SegmentedButton")( + //-- Begin: Basic SegmentedButton SegmentedButton( _.accessibleName := "Geographic location", _.item(_ => "Map"), _.item(_ => "Satellite", _.pressed := true), _.item(_ => "Terrain") ) + //-- End ), - DemoPanel( - "SegmentedButton with Icons", + DemoPanel("SegmentedButton with Icons")( + //-- Begin: SegmentedButton with Icons SegmentedButton( _.item(_.icon := IconName.employee, _.pressed := true), _.item(_.icon := IconName.menu), _.item(_.icon := IconName.factory) ) + //-- End ), - DemoPanel( - "SegmentedButton with 5 SegmentedButtonItems", + DemoPanel("SegmentedButton with 5 SegmentedButtonItems")( + //-- Begin: SegmentedButton with 5 SegmentedButtonItems SegmentedButton( _.item(_ => "Item"), _.item(_ => "Pressed SegmentedButtonItem With Bigger Text", _.pressed := true), @@ -34,6 +38,7 @@ object SegmentedButtonExample extends Example("SegmentedButton") { _.item(_ => "SegmentedButtonItem"), _.item(_ => "Press me") ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/SelectExample.scala b/demo/src/main/scala/demo/SelectExample.scala index 7c95f35..bfd5d83 100644 --- a/demo/src/main/scala/demo/SelectExample.scala +++ b/demo/src/main/scala/demo/SelectExample.scala @@ -3,16 +3,18 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object SelectExample extends Example("SelectExample") { private val someCountries = List("Austria", "Belgium", "Bulgaria", "Germany", "United Kingdom", "Kazakhstan") private val someCountryCodes = List("AT", "BE", "BG", "DE", "UK", "KZ") - def component: HtmlElement = div( - DemoPanel( - "Basic Select", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Select")( + //-- Begin: Basic Select div( Select( _.option(_.icon := IconName.iphone, _ => "Phone"), @@ -25,9 +27,10 @@ object SelectExample extends Example("SelectExample") { _.option(_.icon := IconName.iphone, _ => "Phone") ) ) + //-- End ), - DemoPanel( - "Select with Two-Column Layout Items", + DemoPanel("Select with Two-Column Layout Items")( + //-- Begin: Select with Two-Column Layout Items Select(_ => someCountries .zip(someCountryCodes) @@ -38,6 +41,7 @@ object SelectExample extends Example("SelectExample") { ) ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/ShellBarExample.scala b/demo/src/main/scala/demo/ShellBarExample.scala index 33cad03..aeffb96 100644 --- a/demo/src/main/scala/demo/ShellBarExample.scala +++ b/demo/src/main/scala/demo/ShellBarExample.scala @@ -3,57 +3,59 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} import org.scalajs.dom.HTMLElement object ShellBarExample extends Example("ShellBar") { - def component: HtmlElement = div( - DemoPanel( - "ShellBar", { - val openPopoverBus: EventBus[HTMLElement] = new EventBus - div( - ShellBar( - _.primaryTitle := "Corporate Portal", - _.secondaryTitle := "Secondary title", - _.notificationsCount := "99+", - _.showNotifications := true, - _.showProductSwitch := true, - _.showCoPilot := true, - _.slots.profile := Avatar(_ => img(src := "/images/avatars/sherpal.png")), - _.slots.logo := img(src := "/images/avatars/scala-logo.png"), - _.slots.startButton := Button(_.icon := IconName.`nav-back`), - _.item(_.icon := IconName.disconnected, _.text := "Disconnected"), - _.item(_.icon := IconName.`incoming-call`, _.text := "Incoming Calls", _.count := "4"), - _.slots.searchField := Input(), - _.slots.menuItems := UList.Li(_ => "Application 1"), - _.slots.menuItems := UList.Li(_ => "Application 2"), - _.slots.menuItems := UList.Li(_ => "Application 3"), - _.slots.menuItems := UList.Li(_ => "Application 4"), - _.slots.menuItems := UList.Li(_ => "Application 5"), - _.events.onProfileClick.map(_.detail.targetRef) --> openPopoverBus.writer - ), - Popover( - _ => inContext(el => openPopoverBus.events --> Observer[HTMLElement](el.ref.showAt)), - _.placementType := PopoverPlacementType.Bottom, - _ => div(Title(_ => padding := "0.25rem 1rem 0rem 1rem", _ => "sherpal")), - _ => - div( - UList( - _.separators := ListSeparator.None, - _.Li(_.icon := IconName.`sys-find`, _ => "App Finder"), - _.Li(_.icon := IconName.settings, _ => "Settings"), - _.Li(_.icon := IconName.edit, _ => "Edit Home Page"), - _.Li(_.icon := IconName.`sys-help`, _ => "Help"), - _.Li(_.icon := IconName.log, _ => "Sign out") - ) + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("ShellBar") { + //-- Begin: ShellBar + val openPopoverBus: EventBus[HTMLElement] = new EventBus + div( + ShellBar( + _.primaryTitle := "Corporate Portal", + _.secondaryTitle := "Secondary title", + _.notificationsCount := "99+", + _.showNotifications := true, + _.showProductSwitch := true, + _.showCoPilot := true, + _.slots.profile := Avatar(_ => img(src := "/images/avatars/sherpal.png")), + _.slots.logo := img(src := "/images/avatars/scala-logo.png"), + _.slots.startButton := Button(_.icon := IconName.`nav-back`), + _.item(_.icon := IconName.disconnected, _.text := "Disconnected"), + _.item(_.icon := IconName.`incoming-call`, _.text := "Incoming Calls", _.count := "4"), + _.slots.searchField := Input(), + _.slots.menuItems := UList.item(_ => "Application 1"), + _.slots.menuItems := UList.item(_ => "Application 2"), + _.slots.menuItems := UList.item(_ => "Application 3"), + _.slots.menuItems := UList.item(_ => "Application 4"), + _.slots.menuItems := UList.item(_ => "Application 5"), + _.events.onProfileClick.map(_.detail.targetRef) --> openPopoverBus.writer + ), + Popover( + _ => inContext(el => openPopoverBus.events --> Observer[HTMLElement](el.ref.showAt)), + _.placementType := PopoverPlacementType.Bottom, + _ => div(Title(_ => padding := "0.25rem 1rem 0rem 1rem", _ => "sherpal")), + _ => + div( + UList( + _.separators := ListSeparator.None, + _.item(_.icon := IconName.`sys-find`, _ => "App Finder"), + _.item(_.icon := IconName.settings, _ => "Settings"), + _.item(_.icon := IconName.edit, _ => "Edit Home Page"), + _.item(_.icon := IconName.`sys-help`, _ => "Help"), + _.item(_.icon := IconName.log, _ => "Sign out") ) - ) + ) ) - } - ), - DemoPanel( - "Basic ShellBar", + ) + //-- End + }, + DemoPanel("Basic ShellBar")( + //-- Begin: Basic ShellBar ShellBar( _.primaryTitle := "Corporate Portal", _.secondaryTitle := "secondary title", @@ -61,6 +63,7 @@ object ShellBarExample extends Example("ShellBar") { _.slots.logo := img(src := "/images/avatars/scala-logo.png"), _.slots.startButton := Button(_.icon := IconName.`nav-back`) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/SideNavigationExample.scala b/demo/src/main/scala/demo/SideNavigationExample.scala index 38ff0ac..102b8d6 100644 --- a/demo/src/main/scala/demo/SideNavigationExample.scala +++ b/demo/src/main/scala/demo/SideNavigationExample.scala @@ -3,50 +3,52 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} import org.scalajs.dom.HTMLElement object SideNavigationExample extends Example("SideNavigation") { - def component: HtmlElement = div( - DemoPanel( - "Side Navigation in Application", { - val toggleCollapseBus: EventBus[Unit] = new EventBus + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Side Navigation in Application") { + //-- Begin: Side Navigation in Application + val toggleCollapseBus: EventBus[Unit] = new EventBus - val collapsedSignal = toggleCollapseBus.events.foldLeft(false)((collapsed, _) => !collapsed) - div( - ShellBar( - _.primaryTitle := "UI5 Web Components", - _.secondaryTitle := "The Best Run SAP", - _.showCoPilot := true, - _.slots.startButton := Button( - _.icon := IconName.menu, - _.events.onClick.mapTo(()) --> toggleCollapseBus.writer - ) - ), - SideNavigation( - _.collapsed <-- collapsedSignal, - _.item(_.text := "Home", _.icon := IconName.home), - _.item( - _.text := "People", - _.expanded := true, - _.icon := IconName.group, - _.subItem(_.text := "From My Team"), - _.subItem(_.text := "From Other Team") - ), - _.item(_.text := "Locations", _.icon := IconName.`locate-me`, _.selected := true), - _.item( - _.text := "Events", - _.icon := IconName.calendar, - _.subItem(_.text := "Local"), - _.subItem(_.text := "Others") - ), - _.slots.fixedItems := SideNavigation.item(_.text := "Useful Links", _.icon := IconName.`chain-link`), - _.slots.fixedItems := SideNavigation.item(_.text := "History", _.icon := IconName.history) + val collapsedSignal = toggleCollapseBus.events.foldLeft(false)((collapsed, _) => !collapsed) + div( + ShellBar( + _.primaryTitle := "UI5 Web Components", + _.secondaryTitle := "The Best Run SAP", + _.showCoPilot := true, + _.slots.startButton := Button( + _.icon := IconName.menu, + _.events.onClick.mapTo(()) --> toggleCollapseBus.writer ) + ), + SideNavigation( + _.collapsed <-- collapsedSignal, + _.item(_.text := "Home", _.icon := IconName.home), + _.item( + _.text := "People", + _.expanded := true, + _.icon := IconName.group, + _.subItem(_.text := "From My Team"), + _.subItem(_.text := "From Other Team") + ), + _.item(_.text := "Locations", _.icon := IconName.`locate-me`, _.selected := true), + _.item( + _.text := "Events", + _.icon := IconName.calendar, + _.subItem(_.text := "Local"), + _.subItem(_.text := "Others") + ), + _.slots.fixedItems := SideNavigation.item(_.text := "Useful Links", _.icon := IconName.`chain-link`), + _.slots.fixedItems := SideNavigation.item(_.text := "History", _.icon := IconName.history) ) - } - ) + ) + //-- End + } ) } diff --git a/demo/src/main/scala/demo/SliderExample.scala b/demo/src/main/scala/demo/SliderExample.scala index d617573..7f20172 100644 --- a/demo/src/main/scala/demo/SliderExample.scala +++ b/demo/src/main/scala/demo/SliderExample.scala @@ -3,10 +3,46 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object SliderExample extends Example("Slider") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Slider") { + //-- Begin: Basic Slider + val selectedValueVar: Var[Double] = Var(0) + div( + Label(_ => child.text <-- selectedValueVar.signal.map(value => s"Selected value: $value")), + br(), + Slider(_.value <-- selectedValueVar.signal, _.events.onInput.map(_.target.value) --> selectedValueVar.writer) + ) + //-- End + }, + DemoPanel("Slider with Tooltip") { + //-- Begin: Slider with Tooltip + Slider(_.min := 0, _.max := 20, _.showTooltip := true) + //-- End + }, + DemoPanel("Disabled Slider with Tickmarks and Labels") { + //-- Begin: Disabled Slider with Tickmarks and Labels + Slider(_.min := 20, _.max := 100, _.disabled := true, _.labelInterval := 5, _.showTickmarks := true) + //-- End + }, + DemoPanel("Slider Tooltip, Tickmarks and Labels") { + //-- Begin: Slider Tooltip, Tickmarks and Labels + Slider( + _.min := -20, + _.max := 20, + _.step := 2, + _.value := 12, + _.showTooltip := true, + _.labelInterval := 2, + _.showTickmarks := true + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/SplitButtonExample.scala b/demo/src/main/scala/demo/SplitButtonExample.scala index 9b5aeb1..72ebd52 100644 --- a/demo/src/main/scala/demo/SplitButtonExample.scala +++ b/demo/src/main/scala/demo/SplitButtonExample.scala @@ -3,10 +3,48 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} +import org.scalajs.dom object SplitButtonExample extends Example("SplitButton") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Default SplitButton") { + //-- Begin: Default SplitButton + div(SplitButton(_ => "Default"), SplitButton(_.disabled := true, _ => "Default")) + //-- End + }, + DemoPanel("SplitButton with Design") { + //-- Begin: SplitButton with Design + div(ButtonDesign.allValues.map(design => SplitButton(_.design := design, _ => design.value))) + //-- End + }, + DemoPanel("SplitButton with Icons") { + //-- Begin: SplitButton with Icons + div( + SplitButton(_ => "Icon", _.icon := IconName.add), + SplitButton(_ => "Icon + Active Icon", _.icon := IconName.add, _.activeIcon := IconName.accept) + ) + //-- End + }, + DemoPanel("SplitButton opening Popover on arrow-click") { + //-- Begin: SplitButton opening Popover on arrow-click + val arrowClickBus: EventBus[dom.HTMLElement] = new EventBus + + div( + Popover( + _.showAtFromEvents(arrowClickBus.events), + _ => "Put whatever you want do show on arrow-click." + ), + SplitButton( + _ => "Expand ->", + _.events.onArrowClick.map(_.target) --> arrowClickBus.writer + ) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/StepInputExample.scala b/demo/src/main/scala/demo/StepInputExample.scala index 15d64d8..f21d533 100644 --- a/demo/src/main/scala/demo/StepInputExample.scala +++ b/demo/src/main/scala/demo/StepInputExample.scala @@ -3,10 +3,59 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object StepInputExample extends Example("StepInput") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + //-- Begin Common + styleTag(""" + |.shorter { + | width: 300px; + |} + |""".stripMargin) + //-- End Common + , + DemoPanel("Basic Step Input") { + //-- Begin: Basic Step Input + div( + className := "shorter", + StepInput(_.value := 5), + StepInput(_.readonly := true, _.value := 5), + StepInput(_.disabled := true, _.value := 5) + ) + //-- End + }, + DemoPanel("Step Input with alignment") { + //-- Begin: Step Input with alignment + div( + className := "shorter", + StepInput(_.value := 5), + StepInput(_.value := 5, _ => textAlign := "center"), + StepInput(_.value := 5, _ => textAlign := "right") + ) + //-- End + }, + DemoPanel("Step Input with min, max, step and valuePrecision") { + //-- Begin: Step Input with min, max, step and valuePrecision + div( + className := "shorter", + StepInput(_.value := 5, _.min := 0, _.max := 10, _.step := 1), + StepInput(_.value := 0, _.min := -100, _.max := 100, _.step := 10), + StepInput(_.value := 10, _.min := 0, _.max := 20, _.valuePrecision := 1) + ) + //-- End + }, + DemoPanel("Step Input with Value State") { + //-- Begin: Step Input with Value State + div( + className := "shorter", + ValueState.allValues.map(state => StepInput(_.valueState := state, _ => marginTop := "0.5rem")) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/SwitchExample.scala b/demo/src/main/scala/demo/SwitchExample.scala index 1780fbc..4b97260 100644 --- a/demo/src/main/scala/demo/SwitchExample.scala +++ b/demo/src/main/scala/demo/SwitchExample.scala @@ -3,18 +3,20 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object SwitchExample extends Example("Switch") { - def component: HtmlElement = div( + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( styleTag(""" |div > ui5-switch { | margin-right: 1em; |} |""".stripMargin), - DemoPanel( - "BasicSwitch", + DemoPanel("Basic Switch")( + //-- Begin: Basic Switch div( Switch(_.textOn := "On", _.textOff := "Off"), Switch(_.textOn := "On", _.textOff := "Off", _.checked := true), @@ -22,15 +24,17 @@ object SwitchExample extends Example("Switch") { Switch(_.textOn := "Yes", _.textOff := "No", _.disabled := true), Switch(_.textOn := "Yes", _.textOff := "No", _.checked := true, _.disabled := true) ) + //-- End ), - DemoPanel( - "Graphical Switch", + DemoPanel("Graphical Switch")( + //-- Begin: Graphical Switch div( for { disabled <- List(false, true) checked <- List(false, true) } yield Switch(_.design := SwitchDesign.Graphical, _.checked := checked, _.disabled := disabled) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/TabContainerExample.scala b/demo/src/main/scala/demo/TabContainerExample.scala index d32fe0e..afad086 100644 --- a/demo/src/main/scala/demo/TabContainerExample.scala +++ b/demo/src/main/scala/demo/TabContainerExample.scala @@ -3,13 +3,15 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object TabContainerExample extends Example("TabContainer") { - def component: HtmlElement = div( - DemoPanel( - "Basic TabContainer", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic TabContainer")( + //-- Begin: Basic TabContainer TabContainer( _ => width := "100%", _.tab( @@ -57,9 +59,10 @@ object TabContainerExample extends Example("TabContainer") { ) ) ) + //-- End ), - DemoPanel( - "TabContainer with text only tabs", + DemoPanel("TabContainer with text only tabs")( + //-- Begin: TabContainer with text only tabs TabContainer( _.collapsed := true, _.fixed := true, @@ -69,18 +72,20 @@ object TabContainerExample extends Example("TabContainer") { _.tab(_.text := "About"), _.tab(_.text := "Contacts") ) + //-- End ), - DemoPanel( - "Text only End Overflow", + DemoPanel("Text only End Overflow")( + //-- Begin: Text only End Overflow TabContainer( _ => width := "100%", _.collapsed := true, _.fixed := true, _ => (1 to 23).toList.map(index => TabContainer.tab(_.text := s"Tab $index", _.selected := (index == 13))) ) + //-- End ), - DemoPanel( - "Text only Start and End Overflow", + DemoPanel("Text only Start and End Overflow")( + //-- Begin: Text only Start and End Overflow TabContainer( _ => width := "100%", _.collapsed := true, @@ -88,9 +93,10 @@ object TabContainerExample extends Example("TabContainer") { _.tabsOverflowMode := TabsOverflowMode.StartAndEnd, _ => (1 to 33).toList.map(index => TabContainer.tab(_.text := s"Tab $index", _.selected := (index == 17))) ) + //-- End ), - DemoPanel( - "TabContainer with text and additional-text", + DemoPanel("TabContainer with text and additional-text")( + //-- Begin: TabContainer with text and additional-text TabContainer( _.collapsed := true, _.fixed := true, @@ -100,9 +106,10 @@ object TabContainerExample extends Example("TabContainer") { _.tab(_.text := "Notes", _.additionalText := "16"), _.tab(_.text := "People", _.additionalText := "34") ) + //-- End ), - DemoPanel( - "TabContainer with nested tabs", + DemoPanel("TabContainer with nested tabs")( + //-- Begin: TabContainer with nested tabs TabContainer( _.collapsed := true, _.tab(_.text := "Nodes", _ => "Notes go here ..."), @@ -127,6 +134,7 @@ object TabContainerExample extends Example("TabContainer") { _.slots.subTabs := TabContainer.tab(_.text := "Attachments", _ => "Attachments go here ...") ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/TableExample.scala b/demo/src/main/scala/demo/TableExample.scala index c9293dc..4c7b9f8 100644 --- a/demo/src/main/scala/demo/TableExample.scala +++ b/demo/src/main/scala/demo/TableExample.scala @@ -3,14 +3,16 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example, MTG} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub, MTG} import org.scalajs.dom import scala.scalajs.js object TableExample extends Example("Table") { - def component: HtmlElement = div( + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( styleTag(s""" |.header { | display: flex; @@ -21,42 +23,42 @@ object TableExample extends Example("Table") { | padding: 0.5rem; |} |""".stripMargin), - DemoPanel( - "Basic Table", { - val toggleStickyHeaderBus: EventBus[Unit] = new EventBus - val stickyHeaderSignal = toggleStickyHeaderBus.events.foldLeft(false)((isSticky, _) => !isSticky) + DemoPanel("Basic Table") { + //-- Begin: Basic Table + val toggleStickyHeaderBus: EventBus[Unit] = new EventBus + val stickyHeaderSignal = toggleStickyHeaderBus.events.foldLeft(false)((isSticky, _) => !isSticky) + div( div( - div( - className := "header", - span("Cards table - resize your browser to make some columns pop-in"), - Button(_.events.onClick.mapTo(()) --> toggleStickyHeaderBus, _ => "Toggle Sticky Column Header") + className := "header", + span("Cards table - resize your browser to make some columns pop-in"), + Button(_.events.onClick.mapTo(()) --> toggleStickyHeaderBus, _ => "Toggle Sticky Column Header") + ), + Table( + _.stickyColumnHeader <-- stickyHeaderSignal, + _.slots.columns := Table.column(_ => width := "12rem", _ => span(lineHeight := "1.4rem", "Card")), + _.slots.columns := Table.column(_.minWidth := 800, _ => span(lineHeight := "1.4rem", "Type")), + _.slots.columns := Table.column( + _.minWidth := 600, + _.popinText := "Comment", + _.demandPopin := true, + _ => span(lineHeight := "1.4rem", "Comment") ), - Table( - _.stickyColumnHeader <-- stickyHeaderSignal, - _.slots.columns := Table.column(_ => width := "12rem", _ => span(lineHeight := "1.4rem", "Card")), - _.slots.columns := Table.column(_.minWidth := 800, _ => span(lineHeight := "1.4rem", "Type")), - _.slots.columns := Table.column( - _.minWidth := 600, - _.popinText := "Comment", - _.demandPopin := true, - _ => span(lineHeight := "1.4rem", "Comment") - ), - _.slots.columns := Table.column(_ => span(lineHeight := "1.4rem", "Cost")), - _ => - MTG.cards.map(card => - Table.row( - _.cell(_ => card.name), - _.cell(_ => card.tpe), - _.cell(_ => card.comment), - _.cell(_ => card.cost) - ) + _.slots.columns := Table.column(_ => span(lineHeight := "1.4rem", "Cost")), + _ => + MTG.cards.map(card => + Table.row( + _.cell(_ => card.name), + _.cell(_ => card.tpe), + _.cell(_ => card.comment), + _.cell(_ => card.cost) ) - ) + ) ) - } - ), - DemoPanel( - "Table in SingleSelect-mode", + ) + //-- End + }, + DemoPanel("Table in SingleSelect-mode")( + //-- Begin: Table in SingleSelect-mode Table( _.mode := TableMode.SingleSelect, _.slots.columns := Table.column(_ => width := "12rem", _ => span(lineHeight := "1.4rem", "Card")), @@ -78,9 +80,10 @@ object TableExample extends Example("Table") { ) ) ) + //-- End ), - DemoPanel( - "Table in MultiSelect mode", + DemoPanel("Table in MultiSelect mode")( + //-- Begin: Table in MultiSelect mode" Table( _.events.onSelectionChange.map(_.detail.selectedRows.map(_.dataset.toMap)) --> Observer(println), _.mode := TableMode.MultiSelect, @@ -104,9 +107,10 @@ object TableExample extends Example("Table") { ) ) ) + //-- End ), - DemoPanel( - "Table with No Data", + DemoPanel("Table with No Data")( + //-- Begin: Table with No Data Table( _.noDataText := "No Data", _.slots.columns := Table.column(_ => width := "12rem", _ => span(lineHeight := "1.4rem", "Card")), @@ -119,29 +123,77 @@ object TableExample extends Example("Table") { ), _.slots.columns := Table.column(_ => span(lineHeight := "1.4rem", "Cost")) ) + //-- End ), - DemoPanel( - "Growing Table with 'More' button", { - val loadMoreBus: EventBus[Unit] = new EventBus - val totalNumberOfCards = MTG.cards.length - val numberOfLoadedCards = loadMoreBus.events.delay(3000).mapTo(4).foldLeft(4)(_ + _) + DemoPanel("Growing Table with 'More' button") { + //-- Begin: Growing Table with 'More' button + val loadMoreBus: EventBus[Unit] = new EventBus + val totalNumberOfCards = MTG.cards.length + val numberOfLoadedCards = loadMoreBus.events.delay(3000).mapTo(4).foldLeft(4)(_ + _) + + val cardsToDisplay = numberOfLoadedCards.map(MTG.cards.take) - val cardsToDisplay = numberOfLoadedCards.map(MTG.cards.take) + val busyState = EventStream + .merge( + loadMoreBus.events.mapTo(+1), + numberOfLoadedCards.changes.mapTo(-1) + ) + .foldLeft(0)(_ + _) + .map(_ > 0) - val busyState = EventStream - .merge( - loadMoreBus.events.mapTo(+1), - numberOfLoadedCards.changes.mapTo(-1) + Table( + _.busy <-- busyState, + _.growing := TableGrowingMode.Button, + _.growingButtonSubtext <-- numberOfLoadedCards + .map(_ min totalNumberOfCards) + .map(n => s"[$n / $totalNumberOfCards]"), + _.events.onLoadMore.mapTo(()) --> loadMoreBus, + _.slots.columns := Table.column(_ => width := "12rem", _ => span(lineHeight := "1.4rem", "Card")), + _.slots.columns := Table.column(_.minWidth := 800, _ => span(lineHeight := "1.4rem", "Type")), + _.slots.columns := Table.column( + _.minWidth := 600, + _.popinText := "Comment", + _.demandPopin := true, + _ => span(lineHeight := "1.4rem", "Comment") + ), + _.slots.columns := Table.column(_ => span(lineHeight := "1.4rem", "Cost")), + _ => + children <-- cardsToDisplay.map( + _.map(card => + Table.row( + _ => dataAttr("card-name") := card.name, + _.cell(_ => card.name), + _.cell(_ => card.tpe), + _.cell(_ => card.comment), + _.cell(_ => card.cost) + ) + ) ) - .foldLeft(0)(_ + _) - .map(_ > 0) + ) + //-- End + }, + DemoPanel("Growing Table on Scroll") { + //-- Begin: Growing Table on Scroll + val loadMoreBus: EventBus[Unit] = new EventBus + val totalNumberOfCards = MTG.cards.length + val numberOfLoadedCards = loadMoreBus.events.delay(3000).mapTo(4).foldLeft(4)(_ + _) + + val cardsToDisplay = numberOfLoadedCards.map(MTG.cards.take) + + val busyState = EventStream + .merge( + loadMoreBus.events.mapTo(+1), + numberOfLoadedCards.changes.mapTo(-1) + ) + .foldLeft(0)(_ + _) + .map(_ > 0) + div( + overflowY := "scroll", + height := "400px", Table( _.busy <-- busyState, - _.growing := TableGrowingMode.Button, - _.growingButtonSubtext <-- numberOfLoadedCards - .map(_ min totalNumberOfCards) - .map(n => s"[$n / $totalNumberOfCards]"), + _.growing := TableGrowingMode.Scroll, _.events.onLoadMore.mapTo(()) --> loadMoreBus, _.slots.columns := Table.column(_ => width := "12rem", _ => span(lineHeight := "1.4rem", "Card")), _.slots.columns := Table.column(_.minWidth := 800, _ => span(lineHeight := "1.4rem", "Type")), @@ -165,58 +217,11 @@ object TableExample extends Example("Table") { ) ) ) - } - ), - DemoPanel( - "Growing Table on Scroll", { - val loadMoreBus: EventBus[Unit] = new EventBus - val totalNumberOfCards = MTG.cards.length - val numberOfLoadedCards = loadMoreBus.events.delay(3000).mapTo(4).foldLeft(4)(_ + _) - - val cardsToDisplay = numberOfLoadedCards.map(MTG.cards.take) - - val busyState = EventStream - .merge( - loadMoreBus.events.mapTo(+1), - numberOfLoadedCards.changes.mapTo(-1) - ) - .foldLeft(0)(_ + _) - .map(_ > 0) - - div( - overflowY := "scroll", - height := "400px", - Table( - _.busy <-- busyState, - _.growing := TableGrowingMode.Scroll, - _.events.onLoadMore.mapTo(()) --> loadMoreBus, - _.slots.columns := Table.column(_ => width := "12rem", _ => span(lineHeight := "1.4rem", "Card")), - _.slots.columns := Table.column(_.minWidth := 800, _ => span(lineHeight := "1.4rem", "Type")), - _.slots.columns := Table.column( - _.minWidth := 600, - _.popinText := "Comment", - _.demandPopin := true, - _ => span(lineHeight := "1.4rem", "Comment") - ), - _.slots.columns := Table.column(_ => span(lineHeight := "1.4rem", "Cost")), - _ => - children <-- cardsToDisplay.map( - _.map(card => - Table.row( - _ => dataAttr("card-name") := card.name, - _.cell(_ => card.name), - _.cell(_ => card.tpe), - _.cell(_ => card.comment), - _.cell(_ => card.cost) - ) - ) - ) - ) - ) - } - ), - DemoPanel( - "Table with grouping (SingleSelect)", + ) + //-- End + }, + DemoPanel("Table with grouping (SingleSelect)")( + //-- Begin: Table with grouping (SingleSelect) Table( _.mode := TableMode.SingleSelect, _.slots.columns := Table.column(_ => Label(_ => "City")), @@ -229,6 +234,7 @@ object TableExample extends Example("Table") { _.row(_.cell(_ => "Paris"), _.cell(_ => "10 millions"), _.cell(_ => "France")), _.row(_.cell(_ => "Lille"), _.cell(_ => "1 million"), _.cell(_ => "France")) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/TextAreaExample.scala b/demo/src/main/scala/demo/TextAreaExample.scala index 9577b20..6189ecf 100644 --- a/demo/src/main/scala/demo/TextAreaExample.scala +++ b/demo/src/main/scala/demo/TextAreaExample.scala @@ -3,19 +3,26 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object TextAreaExample extends Example("TextArea") { - def component: HtmlElement = div( - DemoPanel("Basic TextArea", TextArea(_.placeholder := "Type as much text as you wish")), - DemoPanel( - "TextArea with Maximum length", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic TextArea")( + //-- Begin: Basic TextArea + TextArea(_.placeholder := "Type as much text as you wish") + //-- End + ), + DemoPanel("TextArea with Maximum length")( + //-- Begin: TextArea with Maximum length TextArea( _.placeholder := "Type some text", _.maxLength := 10, _.showExceededText := true ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/TimePickerExample.scala b/demo/src/main/scala/demo/TimePickerExample.scala index 4fe37c2..4afa174 100644 --- a/demo/src/main/scala/demo/TimePickerExample.scala +++ b/demo/src/main/scala/demo/TimePickerExample.scala @@ -3,10 +3,43 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object TimePickerExample extends Example("TimePicker") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic TimePicker") { + //-- Begin: Basic TimePicker + val selectedValueBus: EventBus[String] = new EventBus + div( + Label(_ => child.text <-- selectedValueBus.events.map(value => s"Currently selected: $value")), + br(), + TimePicker( + _.events.onChange.map(_.target.value) --> selectedValueBus + ) + ) + //-- End + }, + DemoPanel("TimePicker in twelve hours format") { + //-- Begin: TimePicker in twelve hours format + val selectedValueBus: EventBus[String] = new EventBus + div( + Label(_ => child.text <-- selectedValueBus.events.map(value => s"Currently selected: $value")), + br(), + TimePicker( + _.formatPattern := "hh:mm:ss a", + _.events.onChange.map(_.target.value) --> selectedValueBus + ) + ) + //-- End + }, + DemoPanel("TimePicker with value-state and valueStateMessage") { + //-- Begin: TimePicker with value-state and valueStateMessage + TimePicker(_.valueState := ValueState.Error, _.slots.valueStateMessage := div("Please provide a valid value")) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/TimelineExample.scala b/demo/src/main/scala/demo/TimelineExample.scala index 86e7b88..cb95116 100644 --- a/demo/src/main/scala/demo/TimelineExample.scala +++ b/demo/src/main/scala/demo/TimelineExample.scala @@ -3,13 +3,15 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object TimelineExample extends Example("Timeline") { - def component: HtmlElement = div( - DemoPanel( - "Basic Timeline", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Timeline")( + //-- Begin: Basic Timeline Timeline( _.item( _.titleText := "called", @@ -32,9 +34,10 @@ object TimelineExample extends Example("Timeline") { _ => div("Online meeting") ) ) + //-- End ), - DemoPanel( - "Horizontal timeline", + DemoPanel("Horizontal timeline")( + //-- Begin: Horizontal timeline Timeline( _.layout := TimelineLayout.Horizontal, _.item( @@ -57,6 +60,7 @@ object TimelineExample extends Example("Timeline") { _ => div("Online meeting") ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/TitleExample.scala b/demo/src/main/scala/demo/TitleExample.scala index 6b7255a..01be82f 100644 --- a/demo/src/main/scala/demo/TitleExample.scala +++ b/demo/src/main/scala/demo/TitleExample.scala @@ -3,16 +3,17 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object TitleExample extends Example("Title") { - def component: HtmlElement = div( - DemoPanel( - "Title in All Available Levels", - div( - TitleLevel.allValues.map(level => Title(_.level := level, _ => s"Title level ${level.value.tail}")) - ) + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Title in All Available Levels")( + //-- Begin: Title in All Available Levels + div(TitleLevel.allValues.map(level => Title(_.level := level, _ => s"Title level ${level.value.tail}"))) + //-- End ) ) diff --git a/demo/src/main/scala/demo/ToastExample.scala b/demo/src/main/scala/demo/ToastExample.scala index 581d3cc..5fe9987 100644 --- a/demo/src/main/scala/demo/ToastExample.scala +++ b/demo/src/main/scala/demo/ToastExample.scala @@ -3,71 +3,73 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} import scala.concurrent.duration.DurationInt object ToastExample extends Example("Toast") { - def component: HtmlElement = div( - DemoPanel( - "Basic Toast", { - val toastBus: EventBus[Unit] = new EventBus + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Toast") { + //-- Begin: Basic Toast + val toastBus: EventBus[Unit] = new EventBus - div( - Button(_ => "Basic Toast", _.events.onClick.mapTo(()) --> toastBus.writer), - Toast( - _ => inContext(el => toastBus.events.mapTo(()) --> Observer[Unit](_ => el.ref.show())), - _ => "Basic Toast" - ), - MessageStrip( - _.design := MessageStripDesign.Information, - _ => "This toast pops up thanks to the click event going through an EventBus." - ) + div( + Button(_ => "Basic Toast", _.events.onClick.mapTo(()) --> toastBus.writer), + Toast( + _ => inContext(el => toastBus.events.mapTo(()) --> Observer[Unit](_ => el.ref.show())), + _ => "Basic Toast" + ), + MessageStrip( + _.design := MessageStripDesign.Information, + _ => "This toast pops up thanks to the click event going through an EventBus." ) - } - ), - DemoPanel( - "Toast Duration", { - val shortToastId = "short-toast-id" - val longToastId = "long-toast-id" + ) + //-- End + }, + DemoPanel("Toast Duration") { + //-- Begin: Toast Duration + val shortToastId = "short-toast-id" + val longToastId = "long-toast-id" - div( - Button( - _ => "Short Toast", - _.events.onClick.mapTo(Toast.getToastById(shortToastId)) --> Observer[Option[Toast.Ref]] { - case Some(toast) => toast.show() - case None => throw new IllegalStateException(s"The dom does not contain any toast with id $shortToastId") - } - ), - Toast( - _.id := shortToastId, - _.duration := 1500.millis, - _.placement := ToastPlacement.BottomStart, - _ => "Short Toast" - ), - Button( - _ => "Long Toast", - _.events.onClick.mapTo(Toast.getToastById(longToastId)) --> Observer[Option[Toast.Ref]] { - case Some(toast) => toast.show() - case None => throw new IllegalStateException(s"The dom does not contain any toast with id $longToastId") - } - ), - Toast( - _.id := longToastId, - _.duration := 4500.millis, - _.placement := ToastPlacement.BottomEnd, - _ => "Long Toast" - ), - MessageStrip( - _.design := MessageStripDesign.Information, - _ => "These toasts pops up by grabbing their reference using the Toast.getToastById function." - ) + div( + Button( + _ => "Short Toast", + _.events.onClick.mapTo(Toast.getToastById(shortToastId)) --> Observer[Option[Toast.Ref]] { + case Some(toast) => toast.show() + case None => throw new IllegalStateException(s"The dom does not contain any toast with id $shortToastId") + } + ), + Toast( + _.id := shortToastId, + _.duration := 1500.millis, + _.placement := ToastPlacement.BottomStart, + _ => "Short Toast" + ), + Button( + _ => "Long Toast", + _.events.onClick.mapTo(Toast.getToastById(longToastId)) --> Observer[Option[Toast.Ref]] { + case Some(toast) => toast.show() + case None => throw new IllegalStateException(s"The dom does not contain any toast with id $longToastId") + } + ), + Toast( + _.id := longToastId, + _.duration := 4500.millis, + _.placement := ToastPlacement.BottomEnd, + _ => "Long Toast" + ), + MessageStrip( + _.design := MessageStripDesign.Information, + _ => "These toasts pops up by grabbing their reference using the Toast.getToastById function." ) - } - ), - DemoPanel( - "Toast Placements", + ) + //-- End + }, + DemoPanel("Toast Placements")( + //-- Begin: Toast Placements div( ToastPlacement.allValues.flatMap { placement => val toastBus: EventBus[Unit] = new EventBus @@ -82,6 +84,7 @@ object ToastExample extends Example("Toast") { ) } ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/ToggleButtonExample.scala b/demo/src/main/scala/demo/ToggleButtonExample.scala index 586bbd2..dffc13a 100644 --- a/demo/src/main/scala/demo/ToggleButtonExample.scala +++ b/demo/src/main/scala/demo/ToggleButtonExample.scala @@ -3,10 +3,61 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} object ToggleButtonExample extends Example("ToggleButton") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + styleTag(""" + |ui5-toggle-button { + | margin-right: 0.5rem; + |} + |""".stripMargin), + DemoPanel("ToggleButton States") { + //-- Begin: ToggleButton States + div( + ToggleButton(_ => "ToggleButton"), + ToggleButton(_.pressed := true, _ => "Pressed ToggleButton"), + ToggleButton(_.disabled := true, _ => "Disabled ToggleButton"), + ToggleButton(_.disabled := true, _.pressed := true, _ => "Disabled pressed ToggleButton"), + ToggleButton(_.design := ButtonDesign.Positive, _ => "Accept ToggleButton"), + ToggleButton(_.design := ButtonDesign.Positive, _.pressed := true, _ => "Accept pressed ToggleButton"), + ToggleButton(_.design := ButtonDesign.Negative, _ => "Reject ToggleButton"), + ToggleButton(_.design := ButtonDesign.Negative, _.pressed := true, _ => "Reject pressed ToggleButton"), + ToggleButton(_.design := ButtonDesign.Transparent, _ => "Transparent ToggleButton"), + ToggleButton(_.design := ButtonDesign.Transparent, _.pressed := true, _ => "Transparent pressed ToggleButton") + ) + //-- End + }, + DemoPanel("ToggleButton with Icon") { + //-- Begin: ToggleButton with Icon + div( + ToggleButton(_.icon := IconName.menu, _ => "Menu"), + ToggleButton(_.design := ButtonDesign.Emphasized, _.icon := IconName.add, _ => "Add"), + ToggleButton(_.design := ButtonDesign.Default, _.icon := IconName.`nav-back`, _ => "Back"), + ToggleButton(_.design := ButtonDesign.Positive, _.icon := IconName.accept, _ => "Accept"), + ToggleButton(_.design := ButtonDesign.Negative, _.icon := IconName.`sys-cancel`, _ => "Deny") + ) + //-- End + }, + DemoPanel("ToggleButton with Icon Only") { + //-- Begin: ToggleButton with Icon Only + div( + ToggleButton(_.icon := IconName.accept), + ToggleButton(_.icon := IconName.`action-settings`, _.pressed := true), + ToggleButton(_.icon := IconName.add), + ToggleButton(_.icon := IconName.alert, _.pressed := true), + ToggleButton(_.icon := IconName.away, _.design := ButtonDesign.Positive), + ToggleButton(_.icon := IconName.bookmark, _.design := ButtonDesign.Positive), + ToggleButton(_.icon := IconName.cancel, _.design := ButtonDesign.Negative), + ToggleButton(_.icon := IconName.call, _.design := ButtonDesign.Negative, _.pressed := true), + ToggleButton(_.icon := IconName.camera, _.design := ButtonDesign.Transparent), + ToggleButton(_.icon := IconName.cart, _.design := ButtonDesign.Transparent, _.pressed := true) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/TreeExample.scala b/demo/src/main/scala/demo/TreeExample.scala index b63594a..0d0ac6c 100644 --- a/demo/src/main/scala/demo/TreeExample.scala +++ b/demo/src/main/scala/demo/TreeExample.scala @@ -3,14 +3,16 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} import org.scalajs.dom object TreeExample extends Example("Tree") { - def component: HtmlElement = div( - DemoPanel( - "Basic Tree", + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Basic Tree")( + //-- Begin: Basic Tree Tree( _ => width := "100%", _.item( @@ -40,9 +42,10 @@ object TreeExample extends Example("Tree") { _.expanded := true ) ) + //-- End ), - DemoPanel( - "Tree with multiple selection", + DemoPanel("Tree with multiple selection")( + //-- Begin: Tree with multiple selection Tree( _.mode := ListMode.MultiSelect, _.events.onSelectionChange @@ -75,6 +78,7 @@ object TreeExample extends Example("Tree") { _.expanded := true ) ) + //-- End ) ) diff --git a/demo/src/main/scala/demo/UploadCollectionExample.scala b/demo/src/main/scala/demo/UploadCollectionExample.scala index 0ac51cd..8ff628b 100644 --- a/demo/src/main/scala/demo/UploadCollectionExample.scala +++ b/demo/src/main/scala/demo/UploadCollectionExample.scala @@ -3,10 +3,96 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} +import org.scalajs.dom +import scala.scalajs.js object UploadCollectionExample extends Example("UploadCollection") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + DemoPanel("Upload Collection") { + //-- Begin: Upload Collection + val allStagedFilesVar: Var[List[(dom.File, js.Date)]] = Var(Nil) + + val newFilesArrivedObserver = allStagedFilesVar.updater[List[(dom.File, js.Date)]](_ ++ _) + + val stagedFilesCount = allStagedFilesVar.signal.map(_.length) + + val uploadAllBus: EventBus[Unit] = new EventBus + + UploadCollection( + _.slots.header := div( + Title.h2(_ => child.text <-- stagedFilesCount.map(count => s"Uploaded ($count)")), + Label(_ => "Add new files and press to start uploading pending files:"), + child <-- allStagedFilesVar.signal.mapTo( + // Note that we need to re-create a new file uploader each time, otherwise there is a glitch preventing + // from adding twice the same group of file in a row. (The glitch actually exists on their official docs) + FileUploader( + _.hideInput := true, + _.multiple := true, + _ => Button(_.icon := IconName.add, _.design := ButtonDesign.Transparent), + _.events.onChange.map(_.target.files.map(_ -> new js.Date())) --> newFilesArrivedObserver + ) + ), + Button(_ => "Upload all", _.events.onClick.mapTo(()) --> uploadAllBus.writer) + ), + _.mode := ListMode.Delete, + _.events.onItemDelete.map(_.detail.item.dataset("index").toInt) --> allStagedFilesVar.updater[Int]( + _.patch(_, Nil, 1) + ), + _ => + // This is where the logic of actually uploading files should live + uploadAllBus.events.sample(allStagedFilesVar.signal) --> Observer[List[(dom.File, js.Date)]](files => + files.foreach((file, date) => dom.console.log(file, date)) + ), + _ => + children <-- allStagedFilesVar.signal.map(_.zipWithIndex.map { case ((file, selectedAt), index) => + UploadCollection.item( + _.fileName := file.name, + _.fileNameClickable := true, + _ => s"Selected at: $selectedAt", + _ => dataAttr("index") := index.toString, + _.slots.thumbnail := img( + src := dom.URL.createObjectURL(file), + inContext(el => onLoad --> Observer[Any](_ => dom.URL.revokeObjectURL(el.ref.src))) + ) + ) + }), + _.events.onDrop.preventDefault.map( + _.dataTransfer.files.toList.map(_ -> new js.Date) + ) --> newFilesArrivedObserver + ) + //-- End + }, + DemoPanel("UploadCollection With Drag and Drop and No Initial Data") { + //-- Begin: UploadCollection With Drag and Drop and No Initial Data + val allStagedFilesVar: Var[List[(dom.File, js.Date)]] = Var(Nil) + + val newFilesArrivedObserver = allStagedFilesVar.updater[List[(dom.File, js.Date)]](_ ++ _) + + UploadCollection( + _.slots.header := div(Title.h2(_ => "Attachments")), + _.events.onDrop.preventDefault.map( + _.dataTransfer.files.toList.map(_ -> new js.Date) + ) --> newFilesArrivedObserver, + _ => + children <-- allStagedFilesVar.signal.map(_.zipWithIndex.map { case ((file, selectedAt), index) => + UploadCollection.item( + _.fileName := file.name, + _.fileNameClickable := true, + _ => s"Selected at: $selectedAt", + _ => dataAttr("index") := index.toString, + _.slots.thumbnail := img( + src := dom.URL.createObjectURL(file), + inContext(el => onLoad --> Observer[Any](_ => dom.URL.revokeObjectURL(el.ref.src))) + ) + ) + }) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/ViewSettingsDialogExample.scala b/demo/src/main/scala/demo/ViewSettingsDialogExample.scala index fa5944a..55d3a12 100644 --- a/demo/src/main/scala/demo/ViewSettingsDialogExample.scala +++ b/demo/src/main/scala/demo/ViewSettingsDialogExample.scala @@ -3,10 +3,63 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} +import scala.scalajs.js.JSON object ViewSettingsDialogExample extends Example("ViewSettingsDialog") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + MessageStrip( + _.design := MessageStripDesign.Information, + _ => + "Using the ViewSettingsDialog is straightforward on paper as shown below. However, don't be fooled. You still" + + " need to process by hand whatever it spits out. Given it's dynamic nature, it's not as trivial as it " + + "may seem. (But perhaps adding some magic on top would make it more delightful to use.)" + ), + DemoPanel("Usage") { + //-- Begin: Usage + val settingsBus: EventBus[ViewSettingsDialog.ViewSettings] = new EventBus + val showSettingsDialogBus: EventBus[Unit] = new EventBus + div( + Button(_ => "Open ViewSettingsDialog", _.events.onClick.mapTo(()) --> showSettingsDialogBus.writer), + ViewSettingsDialog( + _.showFromEvents(showSettingsDialogBus.events), + _.events.onCancel.map(_.detail) --> settingsBus.writer, + _.events.onConfirm.map(_.detail) --> settingsBus.writer, + _.slots.sortItems := List( + SortItem(_.text := "Name", _.selected := true), + SortItem(_.text := "Position"), + SortItem(_.text := "Company"), + SortItem(_.text := "Department") + ), + _.slots.filterItems := List( + FilterItem( + _.text := "Position", + _.slots.values := List("CTO", "CPO", "VP").map(position => FilterItem.option(_.text := position)) + ), + FilterItem( + _.text := "Department", + _.slots.values := List("Sales", "Management", "PR").map(position => FilterItem.option(_.text := position)) + ), + FilterItem( + _.text := "Location", + _.slots.values := List("Walldorf", "New York", "London").map(position => + FilterItem.option(_.text := position) + ) + ), + FilterItem( + _.text := "Report to", + _.slots.values := List("CTO", "CPO", "VP").map(position => FilterItem.option(_.text := position)) + ) + ) + ), + pre(child.text <-- settingsBus.events.map(settings => JSON.stringify(settings, space = 2))), + pre(child.text <-- settingsBus.events.map(settings => settings.filters.toString)) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/WizardExample.scala b/demo/src/main/scala/demo/WizardExample.scala index 5d7648c..e3c5471 100644 --- a/demo/src/main/scala/demo/WizardExample.scala +++ b/demo/src/main/scala/demo/WizardExample.scala @@ -3,10 +3,139 @@ package demo import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* -import demo.helpers.{DemoPanel, Example} +import demo.helpers.{DemoPanel, Example, FetchDemoPanelFromGithub} +import com.raquo.laminar.nodes.ReactiveHtmlElement object WizardExample extends Example("Wizard") { - def component: HtmlElement = missing + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( + styleTagForLoginFormClass, + DemoPanel("Wizard") { + //-- Begin: Wizard + val currentStepVar: Var[Int] = Var(1) + + // Singal indicating whether the specified step number is currently selected. + def isSelectedStepSignal(stepNumber: Int) = currentStepVar.signal.map(_ == stepNumber) + + // Biggest step that was seen up to now + val maxSeenStep = currentStepVar.signal.foldLeft(identity)(_ max _) + + // Modifiers for all the steps + def commonModifiers(stepNumber: Int): Mod[ReactiveHtmlElement[WizardStep.Ref]] = List[WizardStep.ModFunction]( + _.selected <-- isSelectedStepSignal(stepNumber), + _.disabled <-- maxSeenStep.map(stepNumber > _), + _ => dataAttr("step-number") := stepNumber.toString + ).map(_(WizardStep)) + + // Creates a button that, when clicking, go to the specified step. Can be hidden temporarily with the observable + def goToStepButton(goTo: Int, hiddenObservable: Observable[Boolean] = Val(false)) = Button( + _.design := ButtonDesign.Emphasized, + _ => s"Go to step $goTo", + _.events.onClick.mapTo(goTo) --> currentStepVar.writer, + _ => hidden <-- hiddenObservable + ) + + // User will not be able to go to step 3 before this is filled. + val maybeInfoForStepThreeVar: Var[Option[String]] = Var(None) + + // This bus will be fed at the very end when the process is done. + val finishDialogBus: EventBus[Unit] = new EventBus + val finishDialogCloseBus: EventBus[Unit] = new EventBus + + div( + Wizard( + _.events.onStepChange.map(_.detail.step.dataset("stepNumber").toInt) --> currentStepVar.writer, + _.step( + _.icon := IconName.home, + _.titleText := "Greeting", + _ => commonModifiers(1), + _ => Title.h3(_ => "1. Greeting"), + _ => + MessageStrip( + _.hideCloseButton := true, + _.design := MessageStripDesign.Information, + _ => + "The Wizard control is supposed to break down large tasks, into smaller steps, easier for the " + + "user to work with." + ), + _ => + div( + "This document is the ultimate authority for Magic: The Gathering® competitive game play. It " + + "consists of a series of numbered rules followed by a glossary. Many of the numbered rules are " + + "divided into subrules, and each separate rule and subrule of the game has its own number. (Note " + + "that subrules skip the letters “l” and “o” due to potential confusion with the numbers “1” and “0”;" + + " subrule 704.5k is followed by 704.5m, then 704.5n, then 704.5p, for example.)" + ), + _ => goToStepButton(2) + ), + _.step( + _.icon := IconName.employee, + _.titleText := "2. User name", + _ => commonModifiers(2), + _ => Title.h3(_ => "2. User name"), + _ => + div( + "Changes may have been made to this document since its publication. You can download the most " + + "recent version from the Magic rules website at Magic.Wizards.com/Rules. If you have questions, " + + "you can get the answers from us at Support.Wizards.com." + ), + _ => + div( + className := loginFormClass, + div( + Label(_ => "Fill in your name to continue:", _.required := true), + Input( + _.events.onChange + .map(_.target.value) + .filter(_.trim.nonEmpty) --> maybeInfoForStepThreeVar.writer.contramapSome + ) + ) + ), + _ => goToStepButton(3, maybeInfoForStepThreeVar.signal.map(_.isEmpty)) + ), + _.step( + _.icon := IconName.`action-settings`, + _.titleText := "3. User profile", + _ => commonModifiers(3), + _ => + Title.h3(_ => + child.text <-- maybeInfoForStepThreeVar.signal.changes + .collect { case Some(name) => name } + .map(name => s"3. User profile: $name") + ), + _ => div("Here the user could fill some optional settings for their profile..."), + _ => goToStepButton(4) + ), + _.step( + _.icon := IconName.`chart-table-view`, + _.titleText := "4. Last Details", + _ => commonModifiers(4), + _ => Title.h4(_ => "4. Last Details"), + _ => div("Here we ask a few last things and then move along with their lives."), + _ => + Button( + _.design := ButtonDesign.Positive, + _ => "Finish!", + _.events.onClick.mapTo(()) --> finishDialogBus.writer + ) + ) + ), + Dialog( + _.showFromEvents(finishDialogBus.events), + _.closeFromEvents(finishDialogCloseBus.events), + _ => div("Process finished!"), + _ => + Button( + _.design := ButtonDesign.Emphasized, + _ => "Done", + _.events.onClick.mapTo(()) --> finishDialogCloseBus.writer + ) + ) + ) + //-- End + } + ) } diff --git a/demo/src/main/scala/demo/facades/highlightjs/HljsLanguage.scala b/demo/src/main/scala/demo/facades/highlightjs/HljsLanguage.scala new file mode 100644 index 0000000..56f2589 --- /dev/null +++ b/demo/src/main/scala/demo/facades/highlightjs/HljsLanguage.scala @@ -0,0 +1,7 @@ +package demo.facades.highlightjs + +import scala.scalajs.js + +/** Marker trait for all js Object that represents languages to be registered with hljs + */ +trait HljsLanguage extends js.Object diff --git a/demo/src/main/scala/demo/facades/highlightjs/hljs.scala b/demo/src/main/scala/demo/facades/highlightjs/hljs.scala new file mode 100644 index 0000000..ed0f468 --- /dev/null +++ b/demo/src/main/scala/demo/facades/highlightjs/hljs.scala @@ -0,0 +1,16 @@ +package demo.facades.highlightjs + +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport + +@js.native +@JSImport("highlight.js/lib/core", JSImport.Default) +object hljs extends js.Object { + + def highlightElement(element: dom.HTMLElement): Unit = js.native + + def registerLanguage(name: String, language: HljsLanguage): Unit = js.native + +} diff --git a/demo/src/main/scala/demo/facades/highlightjs/hljsScala.scala b/demo/src/main/scala/demo/facades/highlightjs/hljsScala.scala new file mode 100644 index 0000000..e3cc30f --- /dev/null +++ b/demo/src/main/scala/demo/facades/highlightjs/hljsScala.scala @@ -0,0 +1,8 @@ +package demo.facades.highlightjs + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport + +@js.native +@JSImport("highlight.js/lib/languages/scala", JSImport.Default) +object hljsScala extends HljsLanguage diff --git a/demo/src/main/scala/demo/helpers/DemoPanel.scala b/demo/src/main/scala/demo/helpers/DemoPanel.scala index ec41067..e81c954 100644 --- a/demo/src/main/scala/demo/helpers/DemoPanel.scala +++ b/demo/src/main/scala/demo/helpers/DemoPanel.scala @@ -3,19 +3,41 @@ package demo.helpers import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* import com.raquo.laminar.api.L.* +import demo.facades.highlightjs.{hljs, hljsScala} object DemoPanel { - def apply(title: String, body: => HtmlElement): HtmlElement = div( + + hljs.registerLanguage("scala", hljsScala) + + def apply(title: String)(body: HtmlElement)(using + demoPanelInfo: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement = div( h2(title), div( padding := "1em", border := "0.0625rem solid #C1C1C1", backgroundColor := "#f7f7f7", body - ) - // div( - // border := "0.0625rem solid #C1C1C1", - // backgroundColor := "#f5f6fa" - // ) + ), + demoPanelInfo.demoPanelInfo + .get(title) + .map(thisExampleInfo => + div( + marginTop := "1em", + overflowX := "auto", + border := "0.0625rem solid #C1C1C1", + backgroundColor := "#f5f6fa", + padding := "1rem", + Title.h3(_ => "Source code"), + pre( + code( + className := "language-scala", + demoPanelInfo.maybeStripIndentCommon.map(_ ++ "\n\n").getOrElse("") ++ + thisExampleInfo.stripIndent, + onMountCallback(ctx => hljs.highlightElement(ctx.thisNode.ref)) + ) + ) + ) + ) ) } diff --git a/demo/src/main/scala/demo/helpers/Example.scala b/demo/src/main/scala/demo/helpers/Example.scala index d75973a..fcfcc4f 100644 --- a/demo/src/main/scala/demo/helpers/Example.scala +++ b/demo/src/main/scala/demo/helpers/Example.scala @@ -3,12 +3,15 @@ package demo.helpers import com.raquo.laminar.api.L.* import be.doeraene.webcomponents.ui5.* import be.doeraene.webcomponents.ui5.configkeys.* +import scala.concurrent.ExecutionContext.Implicits.global /** An instance [[Example]] is bound to a specific component, and is used to display its functionalities. */ trait Example(val name: String) { - def component: HtmlElement + def component(using + demoPanelInfoMap: FetchDemoPanelFromGithub.CompleteDemoPanelInfo + ): HtmlElement def completeComponent = div( Title(_.level := TitleLevel.H1, _ => name), @@ -21,7 +24,12 @@ trait Example(val name: String) { ), "." ), - component + div( + child <-- EventStream + .fromFuture(FetchDemoPanelFromGithub.fetchAllDemoPanelInfo(name)) + .startWith(FetchDemoPanelFromGithub.CompleteDemoPanelInfo(None, Map.empty)) + .map(info => component(using info)) + ) ) def missing: HtmlElement = MessageStrip( @@ -40,6 +48,11 @@ trait Example(val name: String) { |} |""".stripMargin) + def mtgImageWarning = MessageStrip( + _.design := MessageStripDesign.Warning, + _ => "All images displayed on this page are the property of Wizard of the Coast." + ) + } object Example { diff --git a/demo/src/main/scala/demo/helpers/FetchDemoPanelFromGithub.scala b/demo/src/main/scala/demo/helpers/FetchDemoPanelFromGithub.scala new file mode 100644 index 0000000..a202657 --- /dev/null +++ b/demo/src/main/scala/demo/helpers/FetchDemoPanelFromGithub.scala @@ -0,0 +1,131 @@ +package demo.helpers + +import scala.concurrent.Future +import scala.concurrent.ExecutionContext +import org.scalajs.dom.fetch +import org.scalajs.dom +import java.lang.module.ModuleDescriptor.Opens + +object FetchDemoPanelFromGithub { + + private def stripIndentOfString(contents: String): String = { + val lines = contents.split("\n") + val minIndent = lines.filter(_.trim.nonEmpty).map(_.takeWhile(_.isSpaceChar).length).minOption.getOrElse(0) + lines.map(_.drop(minIndent)).mkString("\n") + } + case class CompleteDemoPanelInfo( + maybeCommonContents: Option[String], + demoPanelInfo: Map[String, DemoPanelInfo] + ) { + def maybeStripIndentCommon: Option[String] = maybeCommonContents.map(stripIndentOfString) + } + + case class DemoPanelInfo(title: String, contents: String) { + def stripIndent: String = stripIndentOfString(contents) + } + + trait Parser[T] { self => + def parse(str: String): Option[(T, Int, Int)] + + final def map[U](f: T => U): Parser[U] = new Parser[U] { + def parse(str: String): Option[(U, Int, Int)] = + self.parse(str).map((t, beginIndex, endIndex) => (f(t), beginIndex, endIndex)) + } + + final def flatMap[U](f: T => Parser[U]): Parser[U] = new Parser[U] { + def parse(str: String): Option[(U, Int, Int)] = for { + tResult <- self.parse(str) + (t, beginIndex, endIndex) = tResult + remainingStr = str.drop(endIndex) + uParser = f(t) + uResult <- uParser.parse(remainingStr) + (u, _, endUIndex) = uResult + } yield (u, beginIndex, endUIndex) + } + + final def orElse(default: T): Parser[T] = new Parser[T] { + def parse(str: String): Option[(T, Int, Int)] = Some(self.parse(str).getOrElse((default, 0, str.length))) + } + + final def extract(str: String): Option[T] = parse(str).map(_._1) + } + + /** [[Parser]] succeeding iff `value` is contained as is in the string. */ + def exactMatch(value: String): Parser[Unit] = new Parser[Unit] { + def parse(str: String): Option[(Unit, Int, Int)] = str.indexOf(value) match { + case index if index >= 0 => Some(((), index, index + value.length)) + case _ => None + } + } + + val findCommonExample: Parser[String] = new Parser[String] { + val beginCommonString = "//-- Begin Common" + val endCommonString = "//-- End Common" + + def parse(str: String): Option[(String, Int, Int)] = { + val lines = str.split("\n") + for { + beginLineIndex <- Some(lines.indexWhere(_.trim.startsWith(beginCommonString))).filter(_ >= 0) + numberOfLines <- Some(lines.drop(beginLineIndex).indexWhere(_.trim.startsWith(endCommonString))).filter(_ >= 0) + startIndexInStr = lines.take(beginLineIndex).map(_.length + 1).sum + endIndexInStr = lines.take(beginLineIndex + 1 + numberOfLines).map(_.length + 1).sum + } yield (lines.drop(beginLineIndex + 1).take(numberOfLines - 1).mkString("\n"), startIndexInStr, endIndexInStr) + } + } + + val findNextExample: Parser[DemoPanelInfo] = new Parser[DemoPanelInfo] { + val beginExampleString = "//-- Begin:" + val endExampleString = "//-- End" + + def parse(str: String): Option[(DemoPanelInfo, Int, Int)] = { + val lines = str.split("\n").toVector + Some(lines.indexWhere(_.trim.startsWith(beginExampleString))).filter(_ >= 0).map { lineIndex => + val exampleName = lines(lineIndex).trim.drop(beginExampleString.length).trim + val exampleLines = lines.drop(lineIndex + 1).takeWhile(!_.trim.startsWith(endExampleString)) + val startIndexInStr = lines.take(lineIndex + 1).map(_.length + 1).sum + val endIndexInStr = + lines.drop(lineIndex + 1).take(exampleLines.length + 1).map(_.length + 1).sum + startIndexInStr + (DemoPanelInfo(exampleName, exampleLines.mkString("\n")), startIndexInStr, endIndexInStr) + } + } + } + + def findAll[A](parser: Parser[A]): Parser[List[A]] = new Parser[List[A]] { + def parse(str: String): Option[(List[A], Int, Int)] = + parser.parse(str) match { + case Some((info, beginIndex, endIndex)) => + findAll(parser) + .parse(str.drop(endIndex)) + .map((otherExamples, _, finalIndex) => (info +: otherExamples, beginIndex, endIndex)) + case None => Some((Nil, 0, 0)) + } + } + + val findAllExamples = findAll(findNextExample).orElse(Nil) + val findAllCommonExamples = + findAll(findCommonExample).map(_.mkString("\n\n")).map(Some(_).filter(_.nonEmpty)).orElse(None) + + val completeDemoPanelInfoParser = for { + maybeCommonExamples <- findAllCommonExamples + demoPanelInfo <- findAllExamples + } yield CompleteDemoPanelInfo(maybeCommonExamples, demoPanelInfo.map(info => info.title -> info).toMap) + + private def parseFileContents(content: String): CompleteDemoPanelInfo = + completeDemoPanelInfoParser.extract(content).getOrElse(CompleteDemoPanelInfo(None, Map.empty)) + + def fetchAllDemoPanelInfo(exampleName: String)(using ExecutionContext): Future[CompleteDemoPanelInfo] = (for { + response <- fetch( + s"https://raw.githubusercontent.com/sherpal/LaminarSAPUI5Bindings/master/demo/src/main/scala/demo/${exampleName}Example.scala" + ).toFuture + content <- response.text().toFuture + _ <- + if response.status >= 400 then + Future.failed(new RuntimeException(s"Non successful http status (${response.status}). Body was: $content.")) + else Future.successful(()) + } yield parseFileContents(content)).recover { case throwable: Throwable => + dom.console.error(s"Error while fetching demo panel info: ${throwable.getMessage}") + throwable.printStackTrace() + CompleteDemoPanelInfo(None, Map.empty) + } + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/WebComponent.scala b/web-components/src/main/scala/be/doeraene/webcomponents/WebComponent.scala new file mode 100644 index 0000000..a657e8d --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/WebComponent.scala @@ -0,0 +1,12 @@ +package be.doeraene.webcomponents + +import com.raquo.laminar.api.L.* +import com.raquo.laminar.keys.ReactiveProp + +/** Marker trait that all web components inherit. + * + * This can allow you to implement some shenanigans and abstract over some thins. + */ +trait WebComponent { + val id: ReactiveProp[String, String] = idAttr +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Avatar.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Avatar.scala index db5e40c..f6c50ec 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Avatar.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Avatar.scala @@ -10,6 +10,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** An image-like component that has different display options for representing images and icons in different shapes and * sizes, depending on the use case. The shape can be circular or square. There are several predefined sizes, as well @@ -18,7 +19,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object Avatar extends HasIcon { +object Avatar extends WebComponent with HasIcon { @js.native trait RawElement extends js.Object {} @@ -35,22 +36,24 @@ object Avatar extends HasIcon { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-avatar") - val id: ReactiveProp[String, String] = idAttr + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) - val raised: ReactiveHtmlAttr[Boolean] = + lazy val interactive: ReactiveHtmlAttr[Boolean] = customHtmlAttr("interactive", BooleanAsAttrPresenceCodec) + + lazy val raised: ReactiveHtmlAttr[Boolean] = customHtmlAttr("raised", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val colorScheme: ReactiveHtmlAttr[AvatarColorScheme] = + lazy val colorScheme: ReactiveHtmlAttr[AvatarColorScheme] = customHtmlAttr("color-scheme", AvatarColorScheme.AsStringCodec) - val shape: ReactiveHtmlAttr[AvatarShape] = customHtmlAttr("shape", AvatarShape.AsStringCodec) + lazy val shape: ReactiveHtmlAttr[AvatarShape] = customHtmlAttr("shape", AvatarShape.AsStringCodec) - val initials: ReactiveHtmlAttr[AvatarInitials] = + lazy val initials: ReactiveHtmlAttr[AvatarInitials] = customHtmlAttr("initials", AvatarInitials.AsStringCodec) - val size: ReactiveHtmlAttr[AvatarSize] = customHtmlAttr("size", AvatarSize.AsStringCodec) + lazy val size: ReactiveHtmlAttr[AvatarSize] = customHtmlAttr("size", AvatarSize.AsStringCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Avatar)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/AvatarGroup.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/AvatarGroup.scala new file mode 100644 index 0000000..a447d14 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/AvatarGroup.scala @@ -0,0 +1,76 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.AvatarGroupType +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSImport, JSName} +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.WebComponent + +/** Displays a group of avatars arranged horizontally. It is useful to visually showcase a group of related avatars, + * such as, project team members or employees. The component allows you to display the avatars in different sizes, + * depending on your use case. + * + * @see + * the doc for more + * information. + */ +object AvatarGroup extends WebComponent { + + @js.native + trait RawElement extends js.Object { + @JSName("colorScheme") + def colourSchemeJS: js.Array[String] = js.native + + @JSName("hiddenItems") + def hiddenItemsJS: js.Array[Avatar.Ref] = js.native + } + + object RawElement { + extension (element: RawElement) + def colourScheme: List[String] = element.colourSchemeJS.toList + def hiddenItems: List[Avatar.Ref] = element.hiddenItemsJS.toList + } + + @js.native + @JSImport("@ui5/webcomponents/dist/AvatarGroup.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = AvatarGroup.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-avatar-group") + + lazy val tpe: ReactiveHtmlAttr[AvatarGroupType] = customHtmlAttr("type", AvatarGroupType.AsStringCodec) + + object slots { + val overflowButton: Slot = Slot("overflowButton") + } + + object events { + trait AvatarClickInfo extends js.Object { + def targetRef: dom.HTMLElement + def overflowButtonClicked: Boolean + } + + val onClick: EventProp[EventWithPreciseTarget[Ref] & HasDetail[AvatarClickInfo]] = new EventProp("click") + + val onOverflow: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("overflow") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(AvatarGroup)): _*) + + def avatar: Avatar.type = Avatar + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Badge.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Badge.scala index 88e694f..260bf02 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Badge.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Badge.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-badge is a small non-interactive component which contains text information and color chosen from a list of * predefined color schemes. It serves the purpose to attract the user attention to some piece of information (state, @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object Badge { +object Badge extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -36,9 +37,7 @@ object Badge { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-badge") - val id: ReactiveProp[String, String] = idAttr - - val colourScheme: ReactiveHtmlAttr[ColourScheme] = customHtmlAttr("color-scheme", ColourScheme.AsStringCodec) + lazy val colourScheme: ReactiveHtmlAttr[ColourScheme] = customHtmlAttr("color-scheme", ColourScheme.AsStringCodec) object slots { // note that unlike most elements that have an attribute Icon, this element has a slot icon instead. diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Bar.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Bar.scala index e676855..fd0e889 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Bar.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Bar.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The Bar is a container which is primarily used to hold titles, buttons and input elements and its design and * functionality is the basis for page headers and footers. The component consists of three areas to hold its content - @@ -20,7 +21,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object Bar { +object Bar extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -37,9 +38,7 @@ object Bar { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-bar") - val id: ReactiveProp[String, String] = idAttr - - val design: ReactiveHtmlAttr[BarDesign] = customHtmlAttr("design", BarDesign.AsStringCodec) + lazy val design: ReactiveHtmlAttr[BarDesign] = customHtmlAttr("design", BarDesign.AsStringCodec) object slots { val endContent: Slot = new Slot("endContent") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BarcodeScannerDialog.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BarcodeScannerDialog.scala new file mode 100644 index 0000000..a50d74e --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BarcodeScannerDialog.scala @@ -0,0 +1,78 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.WebComponent + +/** The BarcodeScannerDialog component provides barcode scanning functionality for all devices that support the + * MediaDevices.getUserMedia() native API. Opening the dialog launches the device camera and scans for known barcode + * formats. + * + * @see + * the doc for more + * information. + */ +object BarcodeScannerDialog extends WebComponent { + + @js.native + trait RawElement extends js.Object { + def close(): Unit = js.native + def show(): Unit = js.native + } + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/BarcodeScannerDialog.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = BarcodeScannerDialog.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-barcode-scanner-dialog") + + object slots {} + + object events { + trait ErrorInfo extends js.Object { + def message: String + } + + val onScanError: EventProp[EventWithPreciseTarget[Ref] & HasDetail[ErrorInfo]] = new EventProp("scan-error") + + trait SuccessInfo extends js.Object { + def text: String + def rawBytes: js.typedarray.Uint8Array + } + + val onScanSuccess: EventProp[EventWithPreciseTarget[Ref] & HasDetail[SuccessInfo]] = new EventProp("scan-success") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(BarcodeScannerDialog)): _*) + + /** You can feed this [[Observer]] with a barcode scanner [[Ref]]s in order to close it. */ + val closeObserver: Observer[Ref] = Observer(_.close()) + + /** Can be used as modifier to close the Barcode Scanner every time the stream emits. */ + def closeOnEvents(stream: EventStream[Unit]): Mod[ReactiveHtmlElement[Ref]] = + inContext[ReactiveHtmlElement[Ref]](el => stream.mapTo(el.ref) --> closeObserver) + + /** You can feed this [[Observer]] with a barcode scanner [[Ref]]s in order to open it. */ + val showObserver: Observer[Ref] = Observer(_.show()) + + /** Can be used as modifier to open the Barcode Scanner every time the stream emits. */ + def showOnEvents(stream: EventStream[Unit]): Mod[ReactiveHtmlElement[Ref]] = + inContext[ReactiveHtmlElement[Ref]](el => stream.mapTo(el.ref) --> showObserver) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Breadcrumbs.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Breadcrumbs.scala index c2af9f4..27822a5 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Breadcrumbs.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Breadcrumbs.scala @@ -13,6 +13,7 @@ import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.eventtypes.HasDetail import be.doeraene.webcomponents.ui5.eventtypes.HasItem +import be.doeraene.webcomponents.WebComponent /** Breadcrumbs menu for navigation. * @@ -20,7 +21,7 @@ import be.doeraene.webcomponents.ui5.eventtypes.HasItem * the doc for more * information. */ -object Breadcrumbs extends HasIcon with HasOnClick { +object Breadcrumbs extends WebComponent with HasIcon { @js.native trait RawElement extends js.Object {} @@ -44,12 +45,10 @@ object Breadcrumbs extends HasIcon with HasOnClick { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-breadcrumbs") - val id: ReactiveProp[String, String] = idAttr - - val separatorStyle: ReactiveHtmlAttr[BreadcrumbsSeparatorStyle] = + lazy val separatorStyle: ReactiveHtmlAttr[BreadcrumbsSeparatorStyle] = customHtmlAttr("separator-style", BreadcrumbsSeparatorStyle.AsStringCodec) - val design: ReactiveHtmlAttr[BreadcrumbsDesign] = + lazy val design: ReactiveHtmlAttr[BreadcrumbsDesign] = customHtmlAttr("design", BreadcrumbsDesign.AsStringCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BreadcrumbsItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BreadcrumbsItem.scala index 93e8239..0c017ca 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BreadcrumbsItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BreadcrumbsItem.scala @@ -10,14 +10,15 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent -/** Simple UI button +/** The ui5-breadcrumbs-item component defines the content of an item in ui5-breadcumbs. * * @see * the doc for more * information. */ -object BreadcrumbsItem extends HasIcon with HasOnClick { +object BreadcrumbsItem extends WebComponent with HasIcon { @js.native trait RawElement extends js.Object {} @@ -34,14 +35,12 @@ object BreadcrumbsItem extends HasIcon with HasOnClick { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-breadcrumbs-item") - val id: ReactiveProp[String, String] = idAttr - - val accessibleName: ReactiveHtmlAttr[String] = + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) - val href: ReactiveHtmlAttr[String] = customHtmlAttr("href", StringAsIsCodec) + lazy val href: ReactiveHtmlAttr[String] = customHtmlAttr("href", StringAsIsCodec) - val target: ReactiveHtmlAttr[LinkTarget] = customHtmlAttr("target", LinkTarget.AsStringCodec) + lazy val target: ReactiveHtmlAttr[LinkTarget] = customHtmlAttr("target", LinkTarget.AsStringCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(BreadcrumbsItem)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BusyIndicator.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BusyIndicator.scala index 2235e48..e1ba5df 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BusyIndicator.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/BusyIndicator.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.concurrent.duration.FiniteDuration import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-busy-indicator signals that some operation is going on and that the user must wait. It does not block the * current UI screen so other operations could be triggered in parallel. It displays 3 dots and each dot expands and @@ -21,7 +22,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object BusyIndicator extends HasText { +object BusyIndicator extends WebComponent with HasText { @js.native trait RawElement extends js.Object {} @@ -38,13 +39,11 @@ object BusyIndicator extends HasText { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-busy-indicator") - val id: ReactiveProp[String, String] = idAttr + lazy val active: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("active", BooleanAsAttrPresenceCodec) - val active: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("active", BooleanAsAttrPresenceCodec) + lazy val delay: ReactiveHtmlAttr[FiniteDuration] = customHtmlAttr[FiniteDuration]("delay", FiniteDurationCodec) - val delay: ReactiveHtmlAttr[FiniteDuration] = customHtmlAttr[FiniteDuration]("delay", FiniteDurationCodec) - - val size: ReactiveHtmlAttr[BusyIndicatorSize] = + lazy val size: ReactiveHtmlAttr[BusyIndicatorSize] = customHtmlAttr[BusyIndicatorSize]("size", BusyIndicatorSize.AsStringCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(BusyIndicator)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Button.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Button.scala index f835ac6..7ab2fe6 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Button.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Button.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-button component represents a simple push button. It enables users to trigger actions by clicking or tapping * the ui5-button, or by pressing certain keyboard keys, such as Enter. @@ -18,7 +19,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object Button extends HasIcon with HasOnClick { +object Button extends WebComponent with HasIcon { @js.native trait RawElement extends js.Object {} @@ -35,19 +36,15 @@ object Button extends HasIcon with HasOnClick { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-button") - val id: ReactiveProp[String, String] = idAttr + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val design: ReactiveHtmlAttr[ButtonDesign] = customHtmlAttr("design", ButtonDesign.AsStringCodec) - val design: ReactiveHtmlAttr[ButtonDesign] = customHtmlAttr("design", ButtonDesign.AsStringCodec) + lazy val tooltip: ReactiveHtmlAttr[String] = customHtmlAttr("tooltip", StringAsIsCodec) - val tooltip: ReactiveHtmlAttr[String] = customHtmlAttr("tooltip", StringAsIsCodec) + lazy val iconEnd: ReactiveHtmlAttr[Boolean] = customHtmlAttr("icon-end", BooleanAsAttrPresenceCodec) - val iconEnd: ReactiveHtmlAttr[Boolean] = customHtmlAttr("icon-end", BooleanAsAttrPresenceCodec) - - @js.native - @JSImport("@ui5/webcomponents/dist/features/InputElementsFormSupport.js", JSImport.Default) - object SubmitsSupport extends js.Object + lazy val iconOnly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("icon-only", BooleanAsAttrPresenceCodec) lazy val submits: ReactiveHtmlAttr[Boolean] = { SubmitsSupport diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Calendar.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Calendar.scala new file mode 100644 index 0000000..840f290 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Calendar.scala @@ -0,0 +1,91 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{CalendarSelectionMode, CalendarType} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSImport, JSName} +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.WebComponent + +/** The ui5-calendar component allows users to select one or more dates. + * + * Currently selected dates are represented with instances of ui5-date as children of the ui5-calendar. The value + * property of each ui5-date must be a date string, correctly formatted according to the ui5-calendar's formatPattern + * property. Whenever the user changes the date selection, ui5-calendar will automatically create/remove instances of + * ui5-date in itself, unless you prevent this behavior by calling preventDefault() for the selected-dates-change + * event. This is useful if you want to control the selected dates externally. + * + * @see + * the doc for more + * information. + */ +object Calendar extends WebComponent { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents/dist/Calendar.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = Calendar.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-calendar") + + lazy val hideWeekNumbers: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-week-numbers", BooleanAsAttrPresenceCodec) + + lazy val selectionMode: ReactiveHtmlAttr[CalendarSelectionMode] = + customHtmlAttr("selection-mode", CalendarSelectionMode.AsStringCodec) + + lazy val formatPattern: ReactiveHtmlAttr[String] = customHtmlAttr("format-pattern", StringAsIsCodec) + + lazy val maxDateRaw: ReactiveHtmlAttr[String] = customHtmlAttr("max-date", StringAsIsCodec) + + lazy val minDateRaw: ReactiveHtmlAttr[String] = customHtmlAttr("min-date", StringAsIsCodec) + + lazy val primaryCalendarType: ReactiveHtmlAttr[CalendarType] = + customHtmlAttr("primary-calendar-type", CalendarType.AsStringCodec) + + lazy val secondaryCalendarType: ReactiveHtmlAttr[CalendarType] = + customHtmlAttr("secondary-calendar-type", CalendarType.AsStringCodec) + + object slots {} + + object events { + trait SelectedDatesChangeInfo extends js.Object { + @JSName("values") + def valuesJS: js.Array[String] + + @JSName("dates") + def datesJS: js.Array[Long] + } + + object SelectedDatesChangeInfo { + extension (info: SelectedDatesChangeInfo) + def values: List[String] = info.valuesJS.toList + def dates: List[Long] = info.datesJS.toList + } + + val onSelectedDatesChange: EventProp[EventWithPreciseTarget[Ref] & HasDetail[SelectedDatesChangeInfo]] = + new EventProp( + "selected-dates-change" + ) + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Calendar)): _*) + + def date: CalendarDate.type = CalendarDate + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CalendarDate.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CalendarDate.scala new file mode 100644 index 0000000..5dc9661 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CalendarDate.scala @@ -0,0 +1,39 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSImport, JSName} +import be.doeraene.webcomponents.WebComponent + +/** The ui5-date component defines a calendar date to be used inside ui5-calendar + * + * @see + * the doc for more + * information. + */ +object CalendarDate extends WebComponent with HasValue { + + @js.native + trait RawElement extends js.Object { + def value: String = js.native + } + + type Ref = dom.html.Element with RawElement + type ModFunction = CalendarDate.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-date") + + object slots {} + + object events {} + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(CalendarDate)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Card.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Card.scala index caaf4f4..17a766a 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Card.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Card.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-card is a component that represents information in the form of a tile with separate header and content * areas. @@ -18,7 +19,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object Card extends HasAccessibleName { +object Card extends WebComponent with HasAccessibleName { @js.native trait RawElement extends js.Object {} @@ -35,8 +36,6 @@ object Card extends HasAccessibleName { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-card") - val id: ReactiveProp[String, String] = idAttr - object slots { /** Note: Use ui5-card-header for the intended design. */ diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CardHeader.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CardHeader.scala index 46c54ea..f1e6c0b 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CardHeader.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CardHeader.scala @@ -11,13 +11,14 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-card-header is a component, meant to be used as a header of the ui5-card component. * * @see * the doc for more information. */ -object CardHeader { +object CardHeader extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -34,15 +35,13 @@ object CardHeader { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-card-header") - val id: ReactiveProp[String, String] = idAttr + lazy val interactive: ReactiveHtmlAttr[Boolean] = customHtmlAttr("interactive", BooleanAsAttrPresenceCodec) - val interactive: ReactiveHtmlAttr[Boolean] = customHtmlAttr("interactive", BooleanAsAttrPresenceCodec) + lazy val status: ReactiveHtmlAttr[String] = customHtmlAttr("status", StringAsIsCodec) - val status: ReactiveHtmlAttr[String] = customHtmlAttr("status", StringAsIsCodec) + lazy val subtitleText: ReactiveHtmlAttr[String] = customHtmlAttr("subtitle-text", StringAsIsCodec) - val subtitleText: ReactiveHtmlAttr[String] = customHtmlAttr("subtitle-text", StringAsIsCodec) - - val titleText: ReactiveHtmlAttr[String] = customHtmlAttr("title-text", StringAsIsCodec) + lazy val titleText: ReactiveHtmlAttr[String] = customHtmlAttr("title-text", StringAsIsCodec) object slots { val action: Slot = new Slot("action") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Carousel.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Carousel.scala index 1a583aa..8a8c63e 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Carousel.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Carousel.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The Carousel allows the user to browse through a set of items. The component is mostly used for showing a gallery of * images, but can hold any other HTML element. @@ -20,7 +21,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object Carousel { +object Carousel extends WebComponent { //noinspection ScalaUnusedSymbol @js.native @@ -40,21 +41,20 @@ object Carousel { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-carousel") - val id: ReactiveProp[String, String] = idAttr - - val arrowsPlacement: ReactiveHtmlAttr[CarouselArrowsPlacement] = + lazy val arrowsPlacement: ReactiveHtmlAttr[CarouselArrowsPlacement] = customHtmlAttr("arrows-placement", CarouselArrowsPlacement.AsStringCodec) - val cyclic: ReactiveHtmlAttr[Boolean] = customHtmlAttr("cyclic", BooleanAsAttrPresenceCodec) + lazy val cyclic: ReactiveHtmlAttr[Boolean] = customHtmlAttr("cyclic", BooleanAsAttrPresenceCodec) - val hideNavigationArrows: ReactiveHtmlAttr[Boolean] = + lazy val hideNavigationArrows: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-navigation-arrows", BooleanAsAttrPresenceCodec) - val hidePageIndicator: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-page-indicator", BooleanAsAttrPresenceCodec) + lazy val hidePageIndicator: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("hide-page-indicator", BooleanAsAttrPresenceCodec) - val itemsPerPageL: ReactiveHtmlAttr[Int] = customHtmlAttr("items-per-page-l", IntAsStringCodec) - val itemsPerPageM: ReactiveHtmlAttr[Int] = customHtmlAttr("items-per-page-m", IntAsStringCodec) - val itemsPerPageS: ReactiveHtmlAttr[Int] = customHtmlAttr("items-per-page-s", IntAsStringCodec) + lazy val itemsPerPageL: ReactiveHtmlAttr[Int] = customHtmlAttr("items-per-page-l", IntAsStringCodec) + lazy val itemsPerPageM: ReactiveHtmlAttr[Int] = customHtmlAttr("items-per-page-m", IntAsStringCodec) + lazy val itemsPerPageS: ReactiveHtmlAttr[Int] = customHtmlAttr("items-per-page-s", IntAsStringCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CheckBox.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CheckBox.scala index 3c19443..41070a7 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CheckBox.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CheckBox.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** Allows the user to set a binary value, such as true/false or yes/no for an item. * @@ -18,7 +19,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object CheckBox extends HasIcon with HasAccessibleName with HasName with HasText { +object CheckBox extends WebComponent with HasIcon with HasAccessibleName with HasName with HasText { @js.native trait RawElement extends js.Object { @@ -37,17 +38,15 @@ object CheckBox extends HasIcon with HasAccessibleName with HasName with HasText private val tag: HtmlTag[Ref] = customHtmlTag("ui5-checkbox") - val id: ReactiveProp[String, String] = idAttr + lazy val checked: ReactiveHtmlAttr[Boolean] = customHtmlAttr("checked", BooleanAsAttrPresenceCodec) + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val indeterminate: ReactiveHtmlAttr[Boolean] = customHtmlAttr("indeterminate", BooleanAsAttrPresenceCodec) + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) - val checked: ReactiveHtmlAttr[Boolean] = customHtmlAttr("checked", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val indeterminate: ReactiveHtmlAttr[Boolean] = customHtmlAttr("indeterminate", BooleanAsAttrPresenceCodec) - val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) - val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) - val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) - - val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) + lazy val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPalette.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPalette.scala index 7d99740..baa2b52 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPalette.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPalette.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-color-palette provides the users with a range of predefined colors. The colors are fixed and do not change * with the theme. @@ -20,7 +21,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object ColourPalette { +object ColourPalette extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -37,8 +38,6 @@ object ColourPalette { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-color-palette") - val id: ReactiveProp[String, String] = idAttr - object slots {} object events { diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPaletteItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPaletteItem.scala index f80faa2..288f131 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPaletteItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPaletteItem.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-color-palette-item component represents a color in the the ui5-color-palette. * @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object ColourPaletteItem { +object ColourPaletteItem extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -36,9 +37,7 @@ object ColourPaletteItem { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-color-palette-item") - val id: ReactiveProp[String, String] = idAttr - - val value: ReactiveHtmlAttr[Colour] = customHtmlAttr("value", Colour.AsStringCodec) + lazy val value: ReactiveHtmlAttr[Colour] = customHtmlAttr("value", Colour.AsStringCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPalettePopover.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPalettePopover.scala index 08b5d0d..1b41b54 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPalettePopover.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPalettePopover.scala @@ -13,6 +13,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** Represents a predefined range of colors for easier selection. Overview The ColorPalettePopover provides the users * with a slot to predefine colors. You can customize them with the use of the colors property. You can specify a @@ -24,7 +25,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object ColourPalettePopover { +object ColourPalettePopover extends WebComponent { //noinspection ScalaUnusedSymbol @js.native @@ -44,11 +45,10 @@ object ColourPalettePopover { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-color-palette-popover") - val id: ReactiveProp[String, String] = idAttr + lazy val defaultColour: ReactiveHtmlAttr[Colour] = customHtmlAttr("default-color", Colour.AsStringCodec) - val defaultColour: ReactiveHtmlAttr[Colour] = customHtmlAttr("default-color", Colour.AsStringCodec) - - val showDefaultColour: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-default-color", BooleanAsAttrPresenceCodec) + lazy val showDefaultColour: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("show-default-color", BooleanAsAttrPresenceCodec) /** This import is required for the `showMoreColours` property to work. */ @js.native @@ -60,7 +60,8 @@ object ColourPalettePopover { customHtmlAttr("show-more-colors", BooleanAsAttrPresenceCodec) } - val showRecentColours: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-recent-colors", BooleanAsAttrPresenceCodec) + lazy val showRecentColours: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("show-recent-colors", BooleanAsAttrPresenceCodec) object slots {} @@ -75,4 +76,11 @@ object ColourPalettePopover { def getColourPalettePopoverById(id: String): Option[Ref] = Option(dom.document.getElementById(id)).map(_.asInstanceOf[Ref]) + /** [[Observer]] you can feed a popover ref and a [[dom.HTMLElement]] to open the popover at the element. */ + val showAtObserver: Observer[(Ref, dom.HTMLElement)] = Observer(_ showAt _) + + /** [[Mod]] for [[ColourPalettePopover]]s opening them each time the stream emits an opener [[dom.HTMLElement]] */ + def showAtFromEvents(openerEvents: EventStream[dom.HTMLElement]): Mod[ReactiveHtmlElement[Ref]] = + inContext[ReactiveHtmlElement[Ref]](el => openerEvents.map(el.ref -> _) --> showAtObserver) + } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPicker.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPicker.scala index e885a00..524cd99 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPicker.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ColourPicker.scala @@ -13,6 +13,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.{JSImport, JSName} +import be.doeraene.webcomponents.WebComponent /** The ui5-badge is a small non-interactive component which contains text information and color chosen from a list of * predefined color schemes. It serves the purpose to attract the user attention to some piece of information (state, @@ -22,7 +23,7 @@ import scala.scalajs.js.annotation.{JSImport, JSName} * the doc for more * information. */ -object ColourPicker { +object ColourPicker extends WebComponent { @js.native trait RawElement extends js.Object { @@ -48,9 +49,7 @@ object ColourPicker { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-color-picker") - val id: ReactiveProp[String, String] = idAttr - - val colour: ReactiveHtmlAttr[Colour] = customHtmlAttr("color", Colour.AsStringCodec) + lazy val colour: ReactiveHtmlAttr[Colour] = customHtmlAttr("color", Colour.AsStringCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBox.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBox.scala index b48fe8c..b0b31e3 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBox.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBox.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-combobox component represents a drop-down menu with a list of the available options and a text input field * to narrow down the options. It is commonly used to enable users to select an option from a predefined list. @@ -20,7 +21,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object ComboBox extends HasAccessibleName with HasValue { +object ComboBox extends WebComponent with HasAccessibleName with HasValue { @js.native trait RawElement extends js.Object {} @@ -37,15 +38,13 @@ object ComboBox extends HasAccessibleName with HasValue { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-combobox") - val id: ReactiveProp[String, String] = idAttr - - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val filter: ReactiveHtmlAttr[ComboBoxFilter] = customHtmlAttr("filter", ComboBoxFilter.AsStringCodec) - val loading: ReactiveHtmlAttr[Boolean] = customHtmlAttr("loading", BooleanAsAttrPresenceCodec) - val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) - val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) - val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) - val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val filter: ReactiveHtmlAttr[ComboBoxFilter] = customHtmlAttr("filter", ComboBoxFilter.AsStringCodec) + lazy val loading: ReactiveHtmlAttr[Boolean] = customHtmlAttr("loading", BooleanAsAttrPresenceCodec) + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) object slots { val default: Slot = new Slot("default") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBoxGroupItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBoxGroupItem.scala index e441512..58ac700 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBoxGroupItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBoxGroupItem.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-combobox-group-item is type of suggestion item, that can be used to split the ui5-combobox suggestions into * groups. @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object ComboBoxGroupItem extends HasText { +object ComboBoxGroupItem extends WebComponent with HasText { @js.native trait RawElement extends js.Object { @@ -39,8 +40,6 @@ object ComboBoxGroupItem extends HasText { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-cb-group-item") - val id: ReactiveProp[String, String] = idAttr - object slots {} object events {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBoxItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBoxItem.scala index b30050b..a3fef41 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBoxItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ComboBoxItem.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-cb-item represents the item for a ui5-combobox. * @@ -18,7 +19,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object ComboBoxItem { +object ComboBoxItem extends WebComponent { @js.native trait RawElement extends js.Object { @@ -38,10 +39,8 @@ object ComboBoxItem { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-cb-item") - val id: ReactiveProp[String, String] = idAttr - - val text: ReactiveHtmlAttr[String] = customHtmlAttr("text", StringAsIsCodec) - val additionalText: ReactiveHtmlAttr[String] = customHtmlAttr("additional-text", StringAsIsCodec) + lazy val text: ReactiveHtmlAttr[String] = customHtmlAttr("text", StringAsIsCodec) + lazy val additionalText: ReactiveHtmlAttr[String] = customHtmlAttr("additional-text", StringAsIsCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CustomListItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CustomListItem.scala new file mode 100644 index 0000000..f0bf51f --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/CustomListItem.scala @@ -0,0 +1,55 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent +import be.doeraene.webcomponents.ui5.configkeys.ListItemType +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget + +/** A component to be used as custom list item within the ui5-list the same way as the standard ui5-li. The component + * accepts arbitrary HTML content to allow full customization. + * + * @see + * the doc for more information. + */ +object CustomListItem extends WebComponent { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents/dist/CustomListItem.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = CustomListItem.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-li-custom") + + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) + + lazy val tpe: ReactiveHtmlAttr[ListItemType] = customHtmlAttr("type", ListItemType.AsStringCodec) + + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + + object slots {} + + object events { + val onDetailClick: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("detail-click") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(CustomListItem)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DatePicker.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DatePicker.scala index 4e82b33..075eee3 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DatePicker.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DatePicker.scala @@ -13,6 +13,8 @@ import org.scalajs.dom import java.time.LocalDate import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent /** The ui5-date-picker component provides an input field with assigned calendar which opens on user action. * @@ -20,7 +22,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object DatePicker extends HasOnClick with HasAccessibleName with HasName with HasValue { +object DatePicker extends WebComponent with HasAccessibleName with HasName with HasValue { //noinspection ScalaUnusedSymbol @js.native @@ -63,28 +65,26 @@ object DatePicker extends HasOnClick with HasAccessibleName with HasName with Ha private val tag: HtmlTag[Ref] = customHtmlTag("ui5-date-picker") - val id: ReactiveProp[String, String] = idAttr + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val hideWeekNumbers: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-week-numbers", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val hideWeekNumbers: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-week-numbers", BooleanAsAttrPresenceCodec) + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) - val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) - val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) - val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) - val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + lazy val formatPattern: ReactiveHtmlAttr[String] = customHtmlAttr("format-pattern", StringAsIsCodec) - val formatPattern: ReactiveHtmlAttr[String] = customHtmlAttr("format-pattern", StringAsIsCodec) + lazy val maxDate: ReactiveHtmlAttr[LocalDate] = customHtmlAttr("max-date", LocalDateCodec) + lazy val minDate: ReactiveHtmlAttr[LocalDate] = customHtmlAttr("min-date", LocalDateCodec) - val maxDate: ReactiveHtmlAttr[LocalDate] = customHtmlAttr("max-date", LocalDateCodec) - val minDate: ReactiveHtmlAttr[LocalDate] = customHtmlAttr("min-date", LocalDateCodec) - - val primaryCalendarType: ReactiveHtmlAttr[CalendarType] = + lazy val primaryCalendarType: ReactiveHtmlAttr[CalendarType] = customHtmlAttr("primary-calendar-type", CalendarType.AsStringCodec) - val secondaryCalendarType: ReactiveHtmlAttr[CalendarType] = + lazy val secondaryCalendarType: ReactiveHtmlAttr[CalendarType] = customHtmlAttr("secondary-calendar-type", CalendarType.AsStringCodec) object slots { @@ -92,19 +92,19 @@ object DatePicker extends HasOnClick with HasAccessibleName with HasName with Ha } object events { - @js.native - sealed trait EventData extends js.Object { - def value: String = js.native - def valid: Boolean = js.native + + trait DateEventData extends js.Object { + def value: String + def valid: Boolean } - val onChange = new EventProp[dom.Event & HasDetail[EventData]]("change") - val onInput = new EventProp[dom.Event & HasDetail[EventData]]("input") + val onChange = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[DateEventData]]("change") + val onInput = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[DateEventData]]("input") - val onValidDateChange: EventProcessor[dom.Event & HasDetail[EventData], LocalDate] = + val onValidDateChange: EventProcessor[EventWithPreciseTarget[Ref] & HasDetail[DateEventData], LocalDate] = onChange.map(_.detail).filter(_.valid).map(_.value).map(stringToLocalDate) - val onValidDateInput: EventProcessor[dom.Event & HasDetail[EventData], LocalDate] = + val onValidDateInput: EventProcessor[EventWithPreciseTarget[Ref] & HasDetail[DateEventData], LocalDate] = onInput.map(_.detail).filter(_.valid).map(_.value).map(stringToLocalDate) } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DateRangePicker.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DateRangePicker.scala new file mode 100644 index 0000000..b9561d1 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DateRangePicker.scala @@ -0,0 +1,112 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.* +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSImport, JSName} +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent + +/** The DateRangePicker enables the users to enter a localized date range using touch, mouse, keyboard input, or by + * selecting a date range in the calendar. + * + * @see + * the doc for more + * information. + */ +object DateRangePicker extends WebComponent with HasAccessibleName with HasName with HasValue { + + @js.native + trait RawElement extends js.Object { + @JSName("endDateValue") + def endDateValueJS: js.Date = js.native + + @JSName("startDateValue") + def startDateValueJS: js.Date = js.native + + def closePicker(): Unit = js.native + + def formatValue(date: js.Date): String = js.native + + def isInValidRange(value: String): Boolean = js.native + + def isOpen(): Boolean = js.native + + def isValid(value: String): Boolean = js.native + + def openPicker(): Unit = js.native + } + + @js.native + @JSImport("@ui5/webcomponents/dist/DateRangePicker.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = DateRangePicker.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-daterange-picker") + + lazy val delimiter: ReactiveHtmlAttr[String] = customHtmlAttr("delimiter", StringAsIsCodec) + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val hideWeekNumbers: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-week-numbers", BooleanAsAttrPresenceCodec) + + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) + + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + + lazy val formatPattern: ReactiveHtmlAttr[String] = customHtmlAttr("format-pattern", StringAsIsCodec) + + lazy val maxDateRaw: ReactiveHtmlAttr[String] = customHtmlAttr("max-date", StringAsIsCodec) + + lazy val minDateRaw: ReactiveHtmlAttr[String] = customHtmlAttr("min-date", StringAsIsCodec) + + lazy val primaryCalendarType: ReactiveHtmlAttr[CalendarType] = + customHtmlAttr("primary-calendar-type", CalendarType.AsStringCodec) + + lazy val secondaryCalendarType: ReactiveHtmlAttr[CalendarType] = + customHtmlAttr("secondary-calendar-type", CalendarType.AsStringCodec) + + object slots { + val valueStateMessage: Slot = Slot("valueStateMessage") + } + + object events { + import DatePicker.events.DateEventData + val onChange = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[DateEventData]]("change") + val onInput = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[DateEventData]]("input") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(DateRangePicker)): _*) + + /** You can feed [[DateRangePicker]] refs to this observer in order to close them. */ + val closePickerObserver: Observer[Ref] = Observer(_.closePicker()) + + /** creates a [[Mod]] for your [[DateRangePicker]]s to close them when the stream emit. */ + def closePickerFromEvents(stream: EventStream[Unit]) = + inContext[ReactiveHtmlElement[Ref]](el => stream.mapTo(el.ref) --> closePickerObserver) + + /** You can feed [[DateRangePicker]] refs to this observer in order to open them. */ + val openPickerObserver: Observer[Ref] = Observer(_.openPicker()) + + /** creates a [[Mod]] for your [[DateRangePicker]]s to close them when the stream emit. */ + def openPickerFromEvents(stream: EventStream[Unit]) = + inContext[ReactiveHtmlElement[Ref]](el => stream.mapTo(el.ref) --> openPickerObserver) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DateTimePicker.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DateTimePicker.scala new file mode 100644 index 0000000..ef38c6c --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DateTimePicker.scala @@ -0,0 +1,107 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.* +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSImport, JSName} +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent + +/** The DateTimePicker component alows users to select both date (day, month and year) and time (hours, minutes and + * seconds) and for the purpose it consists of input field and Date/Time picker. + * + * @see + * the doc for more + * information. + */ +object DateTimePicker extends WebComponent with HasAccessibleName with HasName with HasValue { + + @js.native + trait RawElement extends js.Object { + @JSName("dateValue") + def dateValueJS: js.Date = js.native + + def closePicker(): Unit = js.native + + def formatValue(date: js.Date): String = js.native + + def isInValidRange(value: String): Boolean = js.native + + def isOpen(): Boolean = js.native + + def isValid(value: String): Boolean = js.native + + def openPicker(): Unit = js.native + } + + @js.native + @JSImport("@ui5/webcomponents/dist/DateTimePicker.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = DateTimePicker.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-datetime-picker") + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val hideWeekNumbers: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-week-numbers", BooleanAsAttrPresenceCodec) + + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) + + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + + lazy val formatPattern: ReactiveHtmlAttr[String] = customHtmlAttr("format-pattern", StringAsIsCodec) + + lazy val maxDateRaw: ReactiveHtmlAttr[String] = customHtmlAttr("max-date", StringAsIsCodec) + + lazy val minDateRaw: ReactiveHtmlAttr[String] = customHtmlAttr("min-date", StringAsIsCodec) + + lazy val primaryCalendarType: ReactiveHtmlAttr[CalendarType] = + customHtmlAttr("primary-calendar-type", CalendarType.AsStringCodec) + + lazy val secondaryCalendarType: ReactiveHtmlAttr[CalendarType] = + customHtmlAttr("secondary-calendar-type", CalendarType.AsStringCodec) + + object slots { + val valueStateMessage: Slot = Slot("valueStateMessage") + } + + object events { + import DatePicker.events.DateEventData + val onChange = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[DateEventData]]("change") + val onInput = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[DateEventData]]("input") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(DateTimePicker)): _*) + + /** You can feed [[DateTimePicker]] refs to this observer in order to close them. */ + val closePickerObserver: Observer[Ref] = Observer(_.closePicker()) + + /** creates a [[Mod]] for your [[DateTimePicker]]s to close them when the stream emit. */ + def closePickerFromEvents(stream: EventStream[Unit]) = + inContext[ReactiveHtmlElement[Ref]](el => stream.mapTo(el.ref) --> closePickerObserver) + + /** You can feed [[DateTimePicker]] refs to this observer in order to open them. */ + val openPickerObserver: Observer[Ref] = Observer(_.openPicker()) + + /** creates a [[Mod]] for your [[DateTimePicker]]s to close them when the stream emit. */ + def openPickerFromEvents(stream: EventStream[Unit]) = + inContext[ReactiveHtmlElement[Ref]](el => stream.mapTo(el.ref) --> openPickerObserver) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Dialog.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Dialog.scala index b81c3e2..4ef33ac 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Dialog.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Dialog.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-dialog component is used to temporarily display some information in a size-limited window in front of the * regular app screen. @@ -18,7 +19,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object Dialog { +object Dialog extends WebComponent { @js.native trait RawElement extends js.Object { @@ -43,17 +44,15 @@ object Dialog { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-dialog") - val id: ReactiveProp[String, String] = idAttr + lazy val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) - val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) - - val resizable: ReactiveHtmlAttr[Boolean] = customHtmlAttr("resizable", BooleanAsAttrPresenceCodec) - val stretch: ReactiveHtmlAttr[Boolean] = customHtmlAttr("stretch", BooleanAsAttrPresenceCodec) - val draggable: ReactiveHtmlAttr[Boolean] = customHtmlAttr("draggable", BooleanAsAttrPresenceCodec) - val open: ReactiveHtmlAttr[Boolean] = customHtmlAttr("open", BooleanAsAttrPresenceCodec) - val preventFocusRestore: ReactiveHtmlAttr[Boolean] = + lazy val resizable: ReactiveHtmlAttr[Boolean] = customHtmlAttr("resizable", BooleanAsAttrPresenceCodec) + lazy val stretch: ReactiveHtmlAttr[Boolean] = customHtmlAttr("stretch", BooleanAsAttrPresenceCodec) + lazy val draggable: ReactiveHtmlAttr[Boolean] = customHtmlAttr("draggable", BooleanAsAttrPresenceCodec) + lazy val open: ReactiveHtmlAttr[Boolean] = customHtmlAttr("open", BooleanAsAttrPresenceCodec) + lazy val preventFocusRestore: ReactiveHtmlAttr[Boolean] = customHtmlAttr("prevent-focus-restore", BooleanAsAttrPresenceCodec) - val initialFocus: ReactiveHtmlAttr[String] = customHtmlAttr("initial-focus", StringAsIsCodec) + lazy val initialFocus: ReactiveHtmlAttr[String] = customHtmlAttr("initial-focus", StringAsIsCodec) //noinspection TypeAnnotation object slots { @@ -63,7 +62,22 @@ object Dialog { def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Dialog)): _*) - def getDialogById(id: String): Option[dom.HTMLElement & RawElement] = - Option(dom.document.getElementById(id)).map(_.asInstanceOf[dom.HTMLElement & RawElement]) + def getDialogById(id: String): Option[Ref] = + Option(dom.document.getElementById(id)).map(_.asInstanceOf[Ref]) + + /** [[Observer]] you can feed to open the Dialog. */ + val showObserver: Observer[Ref] = Observer(_.show()) + + def showFromEvents(openerEvents: EventStream[Unit]): Mod[ReactiveHtmlElement[Ref]] = + inContext[ReactiveHtmlElement[Ref]](el => openerEvents.mapTo(el.ref) --> showObserver) + + /** [[Observer]] you can feed a [[Dialog]] ref to close it. */ + val closeObserver: Observer[Ref] = Observer(_.close()) + + def closeFromEvents(closeEvents: EventStream[Unit]): Mod[ReactiveHtmlElement[Ref]] = + inContext[ReactiveHtmlElement[Ref]](el => closeEvents.mapTo(el.ref) --> closeObserver) + + /** [[Observer]] you can feed a [[Dialog]] ref to apply focus to it. */ + val applyFocusObserver: Observer[Ref] = Observer(_.applyFocus()) } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DynamicSideContent.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DynamicSideContent.scala new file mode 100644 index 0000000..776aab4 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/DynamicSideContent.scala @@ -0,0 +1,81 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{SideContentFallDown, SideContentPosition, SideContentVisibility} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.WebComponent + +/** The DynamicSideContent (ui5-dynamic-side-content) is a layout component that allows additional content to be + * displayed in a way that flexibly adapts to different screen sizes. The side content appears in a container next to + * or directly below the main content (it doesn't overlay). When the side content is triggered, the main content + * becomes narrower (if appearing side-by-side). The side content contains a separate scrollbar when appearing next to + * the main content. + * + * @see + * the doc for more + * information. + */ +object DynamicSideContent extends WebComponent { + + @js.native + trait RawElement extends js.Object { + def toggleContents(): Unit = js.native + } + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/DynamicSideContent.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = DynamicSideContent.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-dynamic-side-content") + + lazy val equalSplit: ReactiveHtmlAttr[Boolean] = customHtmlAttr("equal-split", BooleanAsAttrPresenceCodec) + + lazy val hideMainContent: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-main-content", BooleanAsAttrPresenceCodec) + + lazy val hideSideContent: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-side-content", BooleanAsAttrPresenceCodec) + + lazy val sideContentFallDown: ReactiveHtmlAttr[SideContentFallDown] = + customHtmlAttr("side-content-fall-down", SideContentFallDown.AsStringCodec) + + lazy val sideContentPosition: ReactiveHtmlAttr[SideContentPosition] = + customHtmlAttr("side-content-position", SideContentPosition.AsStringCodec) + + lazy val sideContentVisibility: ReactiveHtmlAttr[SideContentVisibility] = + customHtmlAttr("side-content-visibility", SideContentVisibility.AsStringCodec) + + object slots { + val sideContent: Slot = new Slot("sideContent") + } + + object events { + trait LayoutChangeInfo extends js.Object { + def currentBreakpoint: String + def previousBreakpoint: String + def mainContentVisible: Boolean + def sideContentVisible: Boolean + } + + val onLayoutChange: EventProp[EventWithPreciseTarget[Ref] & HasDetail[LayoutChangeInfo]] = new EventProp( + "layout-change" + ) + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(DynamicSideContent)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FileUploader.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FileUploader.scala new file mode 100644 index 0000000..43ad34f --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FileUploader.scala @@ -0,0 +1,87 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSImport, JSName} +import org.scalajs.dom.FileList +import be.doeraene.webcomponents.ui5.configkeys.ValueState +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.WebComponent + +/** The ui5-file-uploader opens a file explorer dialog and enables users to upload files. The component consists of + * input field, but you can provide an HTML element by your choice to trigger the file upload, by using the default + * slot. Furthermore, you can set the property "hideInput" to "true" to hide the input field. To get all selected + * files, you can simply use the read-only "files" property. To restrict the types of files the user can select, you + * can use the "accept" property. And, similar to all input based components, the FileUploader supports "valueState", + * "placeholder", "name", and "disabled" properties. For the ui5-file-uploader + * + * @see + * the doc for more + * information. + */ +object FileUploader extends WebComponent with HasName with HasValue { + + @js.native + trait RawElement extends js.Object { + @JSName("files") + def filesJS: FileList = js.native + } + + object RawElement { + extension (element: RawElement) def files: List[dom.File] = element.filesJS.toList + } + + @js.native + @JSImport("@ui5/webcomponents/dist/FileUploader.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = FileUploader.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-file-uploader") + + lazy val accept: ReactiveHtmlAttr[List[String]] = customHtmlAttr("accept", ListCodec(StringAsIsCodec)) + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val hideInput: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-input", BooleanAsAttrPresenceCodec) + + lazy val multiple: ReactiveHtmlAttr[Boolean] = customHtmlAttr("multiple", BooleanAsAttrPresenceCodec) + + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + + object slots { + val valueStateMessage: Slot = new Slot("valueStateMessage") + } + + object events { + + trait HasFiles extends js.Object { + @JSName("files") + def filesJS: FileList + } + + object HasFiles { + extension (element: HasFiles) def files: List[dom.File] = element.filesJS.toList + } + + val onChange: EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasFiles]] = new EventProp("change") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(FileUploader)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FilterItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FilterItem.scala new file mode 100644 index 0000000..33bcb51 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FilterItem.scala @@ -0,0 +1,48 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent + +/** For the ui5-filter-item + * + * @see + * the doc for more + * information. + */ +object FilterItem extends WebComponent with HasText { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/FilterItem.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = FilterItem.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-filter-item") + + object slots { + val values: Slot = Slot("values") + } + + object events {} + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(FilterItem)): _*) + + def option: FilterItemOption.type = FilterItemOption + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FilterItemOption.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FilterItemOption.scala new file mode 100644 index 0000000..b1f3453 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FilterItemOption.scala @@ -0,0 +1,46 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent + +/** For the filter-item-option + * + * @see + * the doc for more + * information. + */ +object FilterItemOption extends WebComponent with HasText { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/FilterItemOption.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = FilterItemOption.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-filter-item-option") + + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + + object slots {} + + object events {} + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(FilterItemOption)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FlexibleColumnLayout.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FlexibleColumnLayout.scala index 1739bfd..58fa8ab 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FlexibleColumnLayout.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/FlexibleColumnLayout.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The FlexibleColumnLayout implements the master-detail-detail paradigm by displaying up to three pages in separate * columns. @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for * more information. */ -object FlexibleColumnLayout { +object FlexibleColumnLayout extends WebComponent { @js.native trait RawElement extends js.Object { @@ -54,11 +55,9 @@ object FlexibleColumnLayout { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-flexible-column-layout") - val id: ReactiveProp[String, String] = idAttr + lazy val hideArrows: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-arrows", BooleanAsAttrPresenceCodec) - val hideArrows: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-arrows", BooleanAsAttrPresenceCodec) - - val layout: ReactiveHtmlAttr[FCLLayout] = customHtmlAttr("layout", FCLLayout.AsStringCodec) + lazy val layout: ReactiveHtmlAttr[FCLLayout] = customHtmlAttr("layout", FCLLayout.AsStringCodec) val onLayoutChanged = new EventProp[dom.Event]("layout-change") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasAccessibleName.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasAccessibleName.scala index 1a1c957..4938fc6 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasAccessibleName.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasAccessibleName.scala @@ -5,6 +5,6 @@ import com.raquo.laminar.api.L.* import com.raquo.laminar.keys.ReactiveHtmlAttr trait HasAccessibleName { - val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) - val accessibleNameRef: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name-ref", StringAsIsCodec) + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) + lazy val accessibleNameRef: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name-ref", StringAsIsCodec) } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasDescription.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasDescription.scala index 216fccb..a0b531c 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasDescription.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasDescription.scala @@ -5,5 +5,5 @@ import com.raquo.laminar.api.L.* import com.raquo.laminar.keys.ReactiveHtmlAttr trait HasDescription { - val description: ReactiveHtmlAttr[String] = customHtmlAttr("description", StringAsIsCodec) + lazy val description: ReactiveHtmlAttr[String] = customHtmlAttr("description", StringAsIsCodec) } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasDesign.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasDesign.scala deleted file mode 100644 index 7aaa28a..0000000 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasDesign.scala +++ /dev/null @@ -1,7 +0,0 @@ -package be.doeraene.webcomponents.ui5 - -import be.doeraene.webcomponents.ui5.configkeys.ButtonDesign -import com.raquo.laminar.api.L.customHtmlAttr -import com.raquo.laminar.keys.ReactiveHtmlAttr - -trait HasDesign {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasIcon.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasIcon.scala index 9cff1ba..e38eac7 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasIcon.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasIcon.scala @@ -5,5 +5,5 @@ import com.raquo.laminar.keys.ReactiveHtmlAttr import com.raquo.laminar.api.L.* trait HasIcon { - val icon: ReactiveHtmlAttr[IconName] = customHtmlAttr("icon", IconName.AsStringCodec) + lazy val icon: ReactiveHtmlAttr[IconName] = customHtmlAttr("icon", IconName.AsStringCodec) } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasName.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasName.scala index 5206a69..277588a 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasName.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasName.scala @@ -5,5 +5,5 @@ import com.raquo.laminar.api.L.* import com.raquo.laminar.keys.ReactiveHtmlAttr trait HasName { - val name: ReactiveHtmlAttr[String] = customHtmlAttr("name", StringAsIsCodec) + lazy val name: ReactiveHtmlAttr[String] = customHtmlAttr("name", StringAsIsCodec) } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasOnChange.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasOnChange.scala deleted file mode 100644 index 24cead5..0000000 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasOnChange.scala +++ /dev/null @@ -1,8 +0,0 @@ -package be.doeraene.webcomponents.ui5 - -import com.raquo.laminar.api.L.EventProp -import org.scalajs.dom - -trait HasOnChange { - val onChange = new EventProp[dom.Event]("change") -} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasOnClick.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasOnClick.scala deleted file mode 100644 index e434a53..0000000 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasOnClick.scala +++ /dev/null @@ -1,8 +0,0 @@ -package be.doeraene.webcomponents.ui5 - -import com.raquo.laminar.api.L.EventProp -import org.scalajs.dom - -trait HasOnClick { - val onClick = new EventProp[dom.MouseEvent]("click") -} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasOnInput.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasOnInput.scala deleted file mode 100644 index ea61b0e..0000000 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasOnInput.scala +++ /dev/null @@ -1,8 +0,0 @@ -package be.doeraene.webcomponents.ui5 - -import com.raquo.laminar.api.L.EventProp -import org.scalajs.dom - -trait HasOnInput { - val onInput = new EventProp[dom.Event]("input") -} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasText.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasText.scala index cc43661..7765572 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasText.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasText.scala @@ -5,5 +5,5 @@ import com.raquo.laminar.api.L.* import com.raquo.laminar.keys.ReactiveHtmlAttr trait HasText { - val text: ReactiveHtmlAttr[String] = customHtmlAttr("text", StringAsIsCodec) + lazy val text: ReactiveHtmlAttr[String] = customHtmlAttr("text", StringAsIsCodec) } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasValue.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasValue.scala index 05dda25..ae8fff6 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasValue.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/HasValue.scala @@ -5,5 +5,5 @@ import com.raquo.laminar.api.L.* import com.raquo.laminar.keys.ReactiveHtmlAttr trait HasValue { - val value: ReactiveHtmlAttr[String] = customHtmlAttr("value", StringAsIsCodec) + lazy val value: ReactiveHtmlAttr[String] = customHtmlAttr("value", StringAsIsCodec) } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Icon.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Icon.scala index b7c63fc..0f78b0c 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Icon.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Icon.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-icon component represents an SVG icon. There are two main scenarios how the ui5-icon component is used: as a * purely decorative element; or as a visually appealing clickable area in the form of an icon button. @@ -18,7 +19,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object Icon extends HasAccessibleName { +object Icon extends WebComponent with HasAccessibleName { @js.native trait RawElement extends js.Object {} @@ -35,14 +36,12 @@ object Icon extends HasAccessibleName { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-icon") - val id: ReactiveProp[String, String] = idAttr + lazy val name: ReactiveHtmlAttr[IconName] = customHtmlAttr("name", IconName.AsStringCodec) - val name: ReactiveHtmlAttr[IconName] = customHtmlAttr("name", IconName.AsStringCodec) - - val interactive: ReactiveHtmlAttr[Boolean] = + lazy val interactive: ReactiveHtmlAttr[Boolean] = customHtmlAttr("interactive", BooleanAsAttrPresenceCodec) - val showTooltip: ReactiveHtmlAttr[Boolean] = + lazy val showTooltip: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-tooltip", BooleanAsAttrPresenceCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Icon)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/IllustratedMessage.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/IllustratedMessage.scala index bcdee26..37b7db7 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/IllustratedMessage.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/IllustratedMessage.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.internal.Slot +import be.doeraene.webcomponents.WebComponent /** Simple UI button * @@ -22,7 +23,7 @@ import be.doeraene.webcomponents.ui5.internal.Slot * the doc for more * information. */ -object IllustratedMessage extends HasIcon with HasOnClick { +object IllustratedMessage extends WebComponent with HasIcon { @js.native trait RawElement extends js.Object {} @@ -39,11 +40,10 @@ object IllustratedMessage extends HasIcon with HasOnClick { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-illustrated-message") - val id: ReactiveProp[String, String] = idAttr - - val name: ReactiveHtmlAttr[IllustrationMessageType] = customHtmlAttr("name", IllustrationMessageType.AsStringCodec) - val subtitleText: ReactiveHtmlAttr[String] = customHtmlAttr("subtitle-text", StringAsIsCodec) - val titleText: ReactiveHtmlAttr[String] = customHtmlAttr("title-text", StringAsIsCodec) + lazy val name: ReactiveHtmlAttr[IllustrationMessageType] = + customHtmlAttr("name", IllustrationMessageType.AsStringCodec) + lazy val subtitleText: ReactiveHtmlAttr[String] = customHtmlAttr("subtitle-text", StringAsIsCodec) + lazy val titleText: ReactiveHtmlAttr[String] = customHtmlAttr("title-text", StringAsIsCodec) object slots { val subtitle: Slot = new Slot("subtitle") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Input.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Input.scala index 2437c99..b0b1622 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Input.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Input.scala @@ -12,16 +12,22 @@ import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.internal.Slot import be.doeraene.webcomponents.ui5.eventtypes.{HasDetail, HasItem, HasTargetRef} +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent -/** Simple UI button +/** The ui5-input component allows the user to enter and edit text or numeric values in one line. + * + * Additionally, you can provide suggestionItems, that are displayed in a popover right under the input. * * @see * the doc for more information. */ -object Input extends HasOnClick with HasOnInput with HasOnChange with HasValue with HasAccessibleName { +object Input extends WebComponent with HasValue with HasAccessibleName { @js.native trait RawElement extends js.Object { + def value: String = js.native + def openPicker(): Unit = js.native } @@ -37,22 +43,20 @@ object Input extends HasOnClick with HasOnInput with HasOnChange with HasValue w private val tag: HtmlTag[Ref] = customHtmlTag("ui5-input") - val id: ReactiveProp[String, String] = idAttr + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) - val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) - val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + lazy val tpe: ReactiveHtmlAttr[InputType] = customHtmlAttr("type", InputType.AsStringCodec) - val tpe: ReactiveHtmlAttr[InputType] = customHtmlAttr("type", InputType.AsStringCodec) + lazy val maxLength: ReactiveHtmlAttr[Int] = customHtmlAttr("maxlength", IntAsStringCodec) - val maxLength: ReactiveHtmlAttr[Int] = customHtmlAttr("maxlength", IntAsStringCodec) + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) - val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) - - val showClearIcon: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-clear-icon", BooleanAsAttrPresenceCodec) - val showSuggestions: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-suggestions", BooleanAsAttrPresenceCodec) + lazy val showClearIcon: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-clear-icon", BooleanAsAttrPresenceCodec) + lazy val showSuggestions: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-suggestions", BooleanAsAttrPresenceCodec) object slots { val valueStateMessage: Slot = new Slot("valueStateMessage") @@ -61,7 +65,10 @@ object Input extends HasOnClick with HasOnInput with HasOnChange with HasValue w val icon: Slot = new Slot("icon") } - object events extends HasOnChange with HasOnInput { + object events { + val onChange: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("change") + val onInput: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("input") + val onSuggestionItemPreview = new EventProp[dom.Event & HasDetail[HasTargetRef[dom.HTMLElement] & HasItem[SuggestionItem.RawElement]]]( "suggestion-item-preview" @@ -72,6 +79,7 @@ object Input extends HasOnClick with HasOnInput with HasOnChange with HasValue w def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Input)): _*) - def suggestion: SuggestionItem.type = SuggestionItem + def suggestion: SuggestionItem.type = SuggestionItem + def suggestionGroup: SuggestionGroupItem.type = SuggestionGroupItem } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Label.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Label.scala index 40eb046..c550371 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Label.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Label.scala @@ -10,13 +10,14 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.configkeys.WrappingType +import be.doeraene.webcomponents.WebComponent /** Simple UI button * * @see * the doc for more information. */ -object Label extends HasIcon with HasOnClick { +object Label extends WebComponent with HasIcon { @js.native trait RawElement extends js.Object {} @@ -33,12 +34,10 @@ object Label extends HasIcon with HasOnClick { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-label") - val id: ReactiveProp[String, String] = idAttr - - val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) - val showColon: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-colon", BooleanAsAttrPresenceCodec) - val forId: ReactiveHtmlAttr[String] = customHtmlAttr("for", StringAsIsCodec) - val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) + lazy val showColon: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-colon", BooleanAsAttrPresenceCodec) + lazy val forId: ReactiveHtmlAttr[String] = customHtmlAttr("for", StringAsIsCodec) + lazy val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) val isRequired: Setter[HtmlElement] = required := true diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Link.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Link.scala index eabbc5d..61f3ecd 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Link.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Link.scala @@ -11,13 +11,15 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.configkeys.WrappingType +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent /** A link to another page. * * @see * the doc for more information. */ -object Link extends HasDesign with HasIcon with HasOnClick with HasAccessibleName { +object Link extends WebComponent with HasIcon with HasAccessibleName { @js.native trait RawElement extends js.Object {} @@ -34,17 +36,21 @@ object Link extends HasDesign with HasIcon with HasOnClick with HasAccessibleNam private val tag: HtmlTag[Ref] = customHtmlTag("ui5-link") - val id: ReactiveProp[String, String] = idAttr - - val disabled: ReactiveHtmlAttr[Boolean] = + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val href: ReactiveHtmlAttr[String] = customHtmlAttr("href", StringAsIsCodec) - val target: ReactiveHtmlAttr[LinkTarget] = customHtmlAttr("target", LinkTarget.AsStringCodec) + lazy val href: ReactiveHtmlAttr[String] = customHtmlAttr("href", StringAsIsCodec) + lazy val target: ReactiveHtmlAttr[LinkTarget] = customHtmlAttr("target", LinkTarget.AsStringCodec) - val design: ReactiveHtmlAttr[LinkDesign] = + lazy val design: ReactiveHtmlAttr[LinkDesign] = customHtmlAttr("design", LinkDesign.AsStringCodec) - val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) + lazy val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) + + object slots {} + + object events { + val onClick: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("click") + } def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Link)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ListItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ListItem.scala index d37fcba..b165d5d 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ListItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ListItem.scala @@ -10,6 +10,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-li represents the simplest type of item for a ui5-list. This is a list item, providing the most common use * cases such as text, image and icon. @@ -17,7 +18,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object ListItem extends HasIcon with HasDescription with HasAdditionalText { +object ListItem extends WebComponent with HasIcon with HasDescription with HasAdditionalText { @js.native trait RawElement extends js.Object {} @@ -34,17 +35,15 @@ object ListItem extends HasIcon with HasDescription with HasAdditionalText { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-li") - val id: ReactiveProp[String, String] = idAttr - - val additionalTextState: ReactiveHtmlAttr[ValueState] = + lazy val additionalTextState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("additional-text-state", ValueState.AsStringCodec) - val iconEnd: ReactiveHtmlAttr[Boolean] = customHtmlAttr("icon-end", BooleanAsAttrPresenceCodec) - val image: ReactiveHtmlAttr[String] = customHtmlAttr("image", StringAsIsCodec) + lazy val iconEnd: ReactiveHtmlAttr[Boolean] = customHtmlAttr("icon-end", BooleanAsAttrPresenceCodec) + lazy val image: ReactiveHtmlAttr[String] = customHtmlAttr("image", StringAsIsCodec) - val tpe: ReactiveHtmlAttr[ListItemType] = customHtmlAttr("type", ListItemType.AsStringCodec) + lazy val tpe: ReactiveHtmlAttr[ListItemType] = customHtmlAttr("type", ListItemType.AsStringCodec) - val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(ListItem)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MediaGallery.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MediaGallery.scala new file mode 100644 index 0000000..5f32814 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MediaGallery.scala @@ -0,0 +1,81 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ + MediaGalleryLayout, + MediaGalleryMenuHorizontalAlign, + MediaGalleryMenuVerticalAlign +} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.ui5.eventtypes.HasItem +import be.doeraene.webcomponents.WebComponent + +/** The ui5-media-gallery component allows the user to browse through multimedia items. Currently, the supported items + * are images and videos. The items should be defined using the ui5-media-gallery-item component. The items are + * initially displayed as thumbnails. When the user selects a thumbnail, the corresponding item is displayed in larger + * size. + * + * The component is responsive by default and adjusts the position of the menu with respect to viewport size, but the + * application is able to further customize the layout via the provided API. + * + * @see + * the doc for more + * information. + */ +object MediaGallery extends WebComponent { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/MediaGallery.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = MediaGallery.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-media-gallery") + + lazy val interactiveDisplayArea: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("interactive-display-area", BooleanAsAttrPresenceCodec) + + lazy val layout: ReactiveHtmlAttr[MediaGalleryLayout] = + customHtmlAttr("layout", MediaGalleryLayout.AsStringCodec) + + lazy val menuHorizontalAlign: ReactiveHtmlAttr[MediaGalleryMenuHorizontalAlign] = + customHtmlAttr("menu-horizontal-align", MediaGalleryMenuHorizontalAlign.AsStringCodec) + + lazy val menuVerticalAlign: ReactiveHtmlAttr[MediaGalleryMenuVerticalAlign] = + customHtmlAttr("menu-vertical-align", MediaGalleryMenuVerticalAlign.AsStringCodec) + + lazy val showAllThumbnails: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("show-all-thumbnails", BooleanAsAttrPresenceCodec) + + object slots {} + + object events { + val onDisplayAreaClick: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("display-area-click") + val onOverflowClick: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("overflow-click") + val onSelectionChange: EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[item.Ref]]] = new EventProp( + "selection-change" + ) + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(MediaGallery)): _*) + + def item: MediaGalleryItem.type = MediaGalleryItem + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MediaGalleryItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MediaGalleryItem.scala new file mode 100644 index 0000000..2f324db --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MediaGalleryItem.scala @@ -0,0 +1,58 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.MediaGalleryItemLayout +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent + +/** The ui5-media-gallery-item web component represents the items displayed in the ui5-media-gallery web component. + * + * Note: ui5-media-gallery-item is not supported when used outside of ui5-media-gallery. + * + * @see + * the doc for more + * information. + */ +object MediaGalleryItem extends WebComponent { + + @js.native + trait RawElement extends js.Object { + def selected: Boolean = js.native + } + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/MediaGalleryItem.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = MediaGalleryItem.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-media-gallery-item") + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val layout: ReactiveHtmlAttr[MediaGalleryItemLayout] = + customHtmlAttr("layout", MediaGalleryItemLayout.AsStringCodec) + + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + + object slots { + val thumbnail: Slot = new Slot("thumbnail") + } + + object events {} + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(MediaGalleryItem)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Menu.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Menu.scala index d163153..f9ccf00 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Menu.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Menu.scala @@ -10,13 +10,14 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** Simple UI button * * @see * the doc for more information. */ -object Menu { +object Menu extends WebComponent { //noinspection ScalaUnusedSymbol @js.native @@ -38,9 +39,7 @@ object Menu { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-menu") - val id: ReactiveProp[String, String] = idAttr - - val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("headerText", StringAsIsCodec) + lazy val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("headerText", StringAsIsCodec) object events { diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MenuItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MenuItem.scala index f350b25..58fe312 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MenuItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MenuItem.scala @@ -9,6 +9,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** ui5-menu-item is the item to use inside a ui5-menu. An arbitrary hierarchy structure can be represented by * recursively nesting menu items. @@ -16,7 +17,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object MenuItem extends HasIcon with HasOnClick with HasText { +object MenuItem extends WebComponent with HasIcon with HasText { @js.native trait RawElement extends js.Object {} @@ -33,11 +34,9 @@ object MenuItem extends HasIcon with HasOnClick with HasText { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-menu-item") - val id: ReactiveProp[String, String] = idAttr + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - - val startsSection: ReactiveHtmlAttr[Boolean] = customHtmlAttr("starts-section", BooleanAsAttrPresenceCodec) + lazy val startsSection: ReactiveHtmlAttr[Boolean] = customHtmlAttr("starts-section", BooleanAsAttrPresenceCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(MenuItem)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MessageStrip.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MessageStrip.scala index 0402c8c..c3752a0 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MessageStrip.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MessageStrip.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-message-strip component enables the embedding of app-related messages. It displays 4 designs of messages, * each with corresponding semantic color and icon: Information, Positive, Warning and Negative. Each message can have @@ -20,7 +21,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object MessageStrip { +object MessageStrip extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -37,13 +38,11 @@ object MessageStrip { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-message-strip") - val id: ReactiveProp[String, String] = idAttr + lazy val design: ReactiveHtmlAttr[MessageStripDesign] = customHtmlAttr("design", MessageStripDesign.AsStringCodec) - val design: ReactiveHtmlAttr[MessageStripDesign] = customHtmlAttr("design", MessageStripDesign.AsStringCodec) + lazy val hideCloseButton: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-close-button", BooleanAsAttrPresenceCodec) - val hideCloseButton: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-close-button", BooleanAsAttrPresenceCodec) - - val hideIcon: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-icon", BooleanAsAttrPresenceCodec) + lazy val hideIcon: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-icon", BooleanAsAttrPresenceCodec) object slots { val icon: Slot = new Slot("icon") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBox.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBox.scala index 1df404f..b0447e2 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBox.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBox.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-combobox component represents a drop-down menu with a list of the available options and a text input field * to narrow down the options. It is commonly used to enable users to select an option from a predefined list. @@ -20,7 +21,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object MultiComboBox extends HasAccessibleName with HasValue { +object MultiComboBox extends WebComponent with HasAccessibleName with HasValue { @js.native trait RawElement extends js.Object {} @@ -37,15 +38,14 @@ object MultiComboBox extends HasAccessibleName with HasValue { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-multi-combobox") - val id: ReactiveProp[String, String] = idAttr - - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val filter: ReactiveHtmlAttr[ComboBoxFilter] = customHtmlAttr("filter", ComboBoxFilter.AsStringCodec) - val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) - val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) - val allowCustomValues: ReactiveHtmlAttr[Boolean] = customHtmlAttr("allow-custom-values", BooleanAsAttrPresenceCodec) - val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) - val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val filter: ReactiveHtmlAttr[ComboBoxFilter] = customHtmlAttr("filter", ComboBoxFilter.AsStringCodec) + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + lazy val allowCustomValues: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("allow-custom-values", BooleanAsAttrPresenceCodec) + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) object slots { val default: Slot = new Slot("default") @@ -63,6 +63,6 @@ object MultiComboBox extends HasAccessibleName with HasValue { def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(MultiComboBox)): _*) - def item: MultiComboBoxItem.type = MultiComboBoxItem - //def group: ComboBoxGroupItem.type = ComboBoxGroupItem // TODO + def item: MultiComboBoxItem.type = MultiComboBoxItem + def group: MultiComboBoxGroupItem.type = MultiComboBoxGroupItem } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBoxGroupItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBoxGroupItem.scala new file mode 100644 index 0000000..7519b2d --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBoxGroupItem.scala @@ -0,0 +1,39 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent + +/** The ui5-mcb-group-item is type of suggestion item, that can be used to split the ui5-multi-combobox suggestions into + * groups. + * + * @see + * the doc for more + * information. + */ +object MultiComboBoxGroupItem extends WebComponent with HasText { + + @js.native + trait RawElement extends js.Object {} + + type Ref = dom.html.Element with RawElement + type ModFunction = MultiComboBoxGroupItem.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-mcb-group-item") + + object slots {} + + object events {} + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(MultiComboBoxGroupItem)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBoxItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBoxItem.scala index 20940ab..6baa268 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBoxItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiComboBoxItem.scala @@ -2,7 +2,7 @@ package be.doeraene.webcomponents.ui5 import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} import be.doeraene.webcomponents.ui5.internal.Slot -import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec, BooleanAsIsCodec} +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, BooleanAsIsCodec, StringAsIsCodec} import com.raquo.laminar.api.L.* import com.raquo.laminar.builders.HtmlTag import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-cb-item represents the item for a ui5-combobox. * @@ -18,7 +19,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object MultiComboBoxItem { +object MultiComboBoxItem extends WebComponent { @js.native trait RawElement extends js.Object { @@ -38,10 +39,9 @@ object MultiComboBoxItem { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-mcb-item") - val id: ReactiveProp[String, String] = idAttr - val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) - val text: ReactiveHtmlAttr[String] = customHtmlAttr("text", StringAsIsCodec) - val additionalText: ReactiveHtmlAttr[String] = customHtmlAttr("additional-text", StringAsIsCodec) + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + lazy val text: ReactiveHtmlAttr[String] = customHtmlAttr("text", StringAsIsCodec) + lazy val additionalText: ReactiveHtmlAttr[String] = customHtmlAttr("additional-text", StringAsIsCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiInput.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiInput.scala new file mode 100644 index 0000000..6672fa1 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/MultiInput.scala @@ -0,0 +1,126 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import com.raquo.domtypes.generic.codecs.IntAsStringCodec +import be.doeraene.webcomponents.ui5.configkeys.InputType +import be.doeraene.webcomponents.ui5.configkeys.ValueState +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.ui5.eventtypes.HasItem +import be.doeraene.webcomponents.WebComponent + +/** A ui5-multi-input field allows the user to enter multiple values, which are displayed as ui5-token. User can choose + * interaction for creating tokens. Fiori Guidelines say that user should create tokens when: + * + * - Type a value in the input and press enter or focus out the input field (change event is fired) + * - Select a value from the suggestion list (suggestion-item-select event is fired) + * + * @see + * the doc for more + * information. + */ +object MultiInput extends WebComponent with HasAccessibleName with HasName with HasValue { + + @js.native + trait RawElement extends js.Object { + def previewItem: suggestion.Ref = js.native + + def openPicker(): Unit = js.native + + def tokensJS: js.Array[token.Ref] = js.native + + def value: String = js.native + } + + object RawElement { + extension (element: RawElement) def tokens: List[token.Ref] = element.tokensJS.toList + } + + @js.native + @JSImport("@ui5/webcomponents/dist/MultiInput.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = MultiInput.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-multi-input") + + lazy val showValueHelpIcon: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("show-value-help-icon", BooleanAsAttrPresenceCodec) + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val maxlength: ReactiveHtmlAttr[Int] = customHtmlAttr("maxlength", IntAsStringCodec) + + // todo[1.4.0] + //lazy val noTypeahead: ReactiveHtmlAttr[Boolean] = customHtmlAttr("no-typeahead", BooleanAsAttrPresenceCodec) + + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + + lazy val showClearIcon: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-clear-icon", BooleanAsAttrPresenceCodec) + + lazy val showSuggestions: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-suggestions", BooleanAsAttrPresenceCodec) + + lazy val tpe: ReactiveHtmlAttr[InputType] = customHtmlAttr("type", InputType.AsStringCodec) + + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + + object slots { + + val tokens: Slot = new Slot("tokens") + + // note that unlike most elements that have an attribute Icon, this element has a slot icon instead. + // most of the time you will want to use a ui5-icon for this slot. + val icon: Slot = new Slot("icon") + + val valueStateMessage: Slot = new Slot("valueStateMessage") + } + + object events { + trait HasToken extends js.Object { + def token: MultiInput.token.Ref + } + + val onTokenDelete: EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasToken]] = new EventProp("token-delete") + + val onValueHelpTrigger: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("value-help-trigger") + + val onChange: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("change") + + val onInput: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("input") + + trait SuggestionItemPreviewInfo extends js.Object { + def item: suggestion.Ref + + def targetRef: suggestion.Ref + } + + val onSuggestionItemPreview: EventProp[EventWithPreciseTarget[Ref] & HasDetail[SuggestionItemPreviewInfo]] = + new EventProp("suggestion-item-preview") + + val onSuggestionItemSelect: EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[suggestion.Ref]]] = + new EventProp("suggestion-item-select") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(MultiInput)): _*) + + def suggestion: SuggestionItem.type = SuggestionItem + + def token: Token.type = Token + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/NotificationAction.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/NotificationAction.scala new file mode 100644 index 0000000..ba6071c --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/NotificationAction.scala @@ -0,0 +1,50 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.ButtonDesign +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent + +/** The ui5-notification-action represents an abstract action, used in the ui5-li-notification and the + * ui5-li-notification-group items. + * + * @see + * the doc for more + * information. + */ +object NotificationAction extends WebComponent with HasText with HasIcon { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/NotificationAction.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = NotificationAction.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-notification-action") + + lazy val design: ReactiveHtmlAttr[ButtonDesign] = customHtmlAttr("design", ButtonDesign.AsStringCodec) + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + object slots {} + + object events {} + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(NotificationAction)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/NotificationListGroupItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/NotificationListGroupItem.scala new file mode 100644 index 0000000..31fd801 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/NotificationListGroupItem.scala @@ -0,0 +1,72 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, IconName, Priority} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import scala.concurrent.duration.FiniteDuration +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent +import be.doeraene.webcomponents.ui5.configkeys.AvatarSize.L + +/** The ui5-li-notification-group is a special type of list item, that unlike others can group items within self, + * usually ui5-li-notification items. + * + * @see + * the doc for + * more information. + */ +object NotificationListGroupItem extends WebComponent { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/NotificationListGroupItem.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = NotificationListGroupItem.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-li-notification-group") + + lazy val collapsed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("collapsed", BooleanAsAttrPresenceCodec) + + lazy val showCounter: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-counter", BooleanAsAttrPresenceCodec) + + lazy val busy: ReactiveHtmlAttr[Boolean] = customHtmlAttr("busy", BooleanAsAttrPresenceCodec) + + lazy val busyDelay: ReactiveHtmlAttr[FiniteDuration] = customHtmlAttr("busy-delay", FiniteDurationCodec) + + lazy val priority: ReactiveHtmlAttr[Priority] = customHtmlAttr("priority", Priority.AsStringCodec) + + lazy val read: ReactiveHtmlAttr[Boolean] = customHtmlAttr("read", BooleanAsAttrPresenceCodec) + + lazy val showClose: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-close", BooleanAsAttrPresenceCodec) + + lazy val titleText: ReactiveHtmlAttr[String] = customHtmlAttr("title-text", StringAsIsCodec) + + object slots { + val actions: Slot = Slot("actions") + } + + object events { + val onToggle: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("toggle") + val onClose: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("close") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(NotificationListGroupItem)): _*) + + def item: NotificationListItem.type = NotificationListItem + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/NotificationListItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/NotificationListItem.scala new file mode 100644 index 0000000..70871d0 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/NotificationListItem.scala @@ -0,0 +1,73 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.Priority +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.configkeys.WrappingType +import scala.concurrent.duration.FiniteDuration +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent + +/** The ui5-li-notification is a type of list item, meant to display notifications. + * + * The component has a rich set of various properties that allows the user to set avatar, titleText, descriptive + * content and footnotes to fully describe a notification. + * + * @see + * the doc for more + * information. + */ +object NotificationListItem extends WebComponent { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/NotificationListItem.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = NotificationListItem.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-li-notification") + + lazy val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) + + lazy val busy: ReactiveHtmlAttr[Boolean] = customHtmlAttr("busy", BooleanAsAttrPresenceCodec) + + lazy val busyDelay: ReactiveHtmlAttr[FiniteDuration] = customHtmlAttr("busy-delay", FiniteDurationCodec) + + lazy val priority: ReactiveHtmlAttr[Priority] = customHtmlAttr("priority", Priority.AsStringCodec) + + lazy val read: ReactiveHtmlAttr[Boolean] = customHtmlAttr("read", BooleanAsAttrPresenceCodec) + + lazy val showClose: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-close", BooleanAsAttrPresenceCodec) + + lazy val titleText: ReactiveHtmlAttr[String] = customHtmlAttr("title-text", StringAsIsCodec) + + object slots { + val avatar: Slot = Slot("avatar") + val footnotes: Slot = Slot("footnotes") + val actions: Slot = Slot("actions") + } + + object events { + val onClose: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("close") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(NotificationListItem)): _*) + + def action: NotificationAction.type = NotificationAction + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Page.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Page.scala new file mode 100644 index 0000000..6dd22a0 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Page.scala @@ -0,0 +1,60 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.PageBackgroundDesign +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent + +/** The ui5-page is a container component that holds one whole screen of an application. The page has three distinct + * areas that can hold content - a header, content area and a footer. + * + * @see + * the doc for more information. + */ +object Page extends WebComponent { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/Page.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = Page.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-page") + + lazy val backgroundDesign: ReactiveHtmlAttr[PageBackgroundDesign] = + customHtmlAttr("background-design", PageBackgroundDesign.AsStringCodec) + + lazy val disableScrolling: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("disable-scrolling", BooleanAsAttrPresenceCodec) + + lazy val floatingFooter: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("floating-footer", BooleanAsAttrPresenceCodec) + + lazy val hideFooter: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("hide-footer", BooleanAsAttrPresenceCodec) + + object slots { + val footer: Slot = new Slot("footer") + val header: Slot = new Slot("header") + } + + object events {} + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Page)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Panel.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Panel.scala index 27713a5..33356e2 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Panel.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Panel.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-panel component is a container which has a header and a content area and is used for grouping and displaying * information. It can be collapsed to save space on the screen. @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object Panel extends HasAccessibleName { +object Panel extends WebComponent with HasAccessibleName { @js.native trait RawElement extends js.Object { @@ -38,18 +39,16 @@ object Panel extends HasAccessibleName { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-panel") - val id: ReactiveProp[String, String] = idAttr - - val accessibleRole: ReactiveHtmlAttr[PanelAccessibleRole] = + lazy val accessibleRole: ReactiveHtmlAttr[PanelAccessibleRole] = customHtmlAttr("accessible-role", PanelAccessibleRole.AsStringCodec) - val collapsed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("collapsed", BooleanAsAttrPresenceCodec) - val fixed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("fixed", BooleanAsAttrPresenceCodec) + lazy val collapsed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("collapsed", BooleanAsAttrPresenceCodec) + lazy val fixed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("fixed", BooleanAsAttrPresenceCodec) - val headerLevel: ReactiveHtmlAttr[TitleLevel] = customHtmlAttr("header-level", TitleLevel.AsStringCodec) - val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) + lazy val headerLevel: ReactiveHtmlAttr[TitleLevel] = customHtmlAttr("header-level", TitleLevel.AsStringCodec) + lazy val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) - val noAnimation: ReactiveHtmlAttr[Boolean] = customHtmlAttr("no-animation", BooleanAsAttrPresenceCodec) + lazy val noAnimation: ReactiveHtmlAttr[Boolean] = customHtmlAttr("no-animation", BooleanAsAttrPresenceCodec) object slots { val header: Slot = new Slot("header") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Popover.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Popover.scala index a7df1e6..54cf1de 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Popover.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Popover.scala @@ -1,6 +1,6 @@ package be.doeraene.webcomponents.ui5 -import be.doeraene.webcomponents.ui5.configkeys.PopoverPlacementType +import be.doeraene.webcomponents.ui5.configkeys.{PopoverHorizontalAlign, PopoverPlacementType, PopoverVerticalAlign} import be.doeraene.webcomponents.ui5.internal.Slot import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} import com.raquo.laminar.api.L.* @@ -11,6 +11,9 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.WebComponent /** The ui5-popover component displays additional information for an object in a compact way and without leaving the * page. @@ -18,7 +21,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object Popover extends HasIcon with HasOnClick { +object Popover extends WebComponent with HasAccessibleName { @js.native trait RawElement extends js.Object { @@ -43,18 +46,35 @@ object Popover extends HasIcon with HasOnClick { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-popover") - val id: ReactiveProp[String, String] = idAttr + lazy val allowTargetOverlap: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("allow-target-overlap", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) - val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) + lazy val hideArrow: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-arrow", BooleanAsAttrPresenceCodec) - val placementType: ReactiveHtmlAttr[PopoverPlacementType] = - customHtmlAttr("placement-type", PopoverPlacementType.AsStringCodec) + lazy val hideBackdrop: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-backdrop", BooleanAsAttrPresenceCodec) + + lazy val horizontalAlign: ReactiveHtmlAttr[PopoverHorizontalAlign] = + customHtmlAttr("horizontal-align", PopoverHorizontalAlign.AsStringCodec) + + lazy val modal: ReactiveHtmlAttr[Boolean] = customHtmlAttr("modal", BooleanAsAttrPresenceCodec) /** id of the element that opens the popover */ - val opener: ReactiveHtmlAttr[String] = customHtmlAttr("opener", StringAsIsCodec) - val open: ReactiveHtmlAttr[Boolean] = customHtmlAttr("open", BooleanAsAttrPresenceCodec) + lazy val opener: ReactiveHtmlAttr[String] = customHtmlAttr("opener", StringAsIsCodec) + + lazy val placementType: ReactiveHtmlAttr[PopoverPlacementType] = + customHtmlAttr("placement-type", PopoverPlacementType.AsStringCodec) + + lazy val verticalAlign: ReactiveHtmlAttr[PopoverVerticalAlign] = + customHtmlAttr("vertical-align", PopoverVerticalAlign.AsStringCodec) + + lazy val initialFocus: ReactiveHtmlAttr[String] = customHtmlAttr("initial-focus", StringAsIsCodec) + + lazy val open: ReactiveHtmlAttr[Boolean] = customHtmlAttr("open", BooleanAsAttrPresenceCodec) + + lazy val preventFocusRestore: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("prevent-focus-restore", BooleanAsAttrPresenceCodec) object slots { def header: Slot = new Slot("header") @@ -62,10 +82,17 @@ object Popover extends HasIcon with HasOnClick { } object events { - val onAfterClose: EventProp[dom.Event] = new EventProp("after-close") - val onAfterOpen: EventProp[dom.Event] = new EventProp("after-open") - val onBeforeClose: EventProp[dom.Event] = new EventProp("before-close") - val onBeforeOpen: EventProp[dom.Event] = new EventProp("before-open") + val onAfterClose: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("after-close") + val onAfterOpen: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("after-open") + + trait BeforeCloseInfo extends js.Object { + def escPressed: Boolean + } + + val onBeforeClose: EventProp[EventWithPreciseTarget[Ref] & HasDetail[BeforeCloseInfo]] = new EventProp( + "before-close" + ) + val onBeforeOpen: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("before-open") } def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Popover)): _*) @@ -73,4 +100,21 @@ object Popover extends HasIcon with HasOnClick { def getPopoverById(id: String): Option[Ref] = Option(dom.document.getElementById(id)).map(_.asInstanceOf[dom.HTMLElement & RawElement]) + /** [[Observer]] you can feed a popover ref and a [[dom.HTMLElement]] to open the popover at the element. */ + val showAtObserver: Observer[(Ref, dom.HTMLElement)] = Observer(_ showAt _) + + /** [[Mod]] for [[Popover]]s opening them each time the stream emits an opener [[dom.HTMLElement]] */ + def showAtFromEvents(openerEvents: EventStream[dom.HTMLElement]): Mod[ReactiveHtmlElement[Ref]] = + inContext[ReactiveHtmlElement[Ref]](el => openerEvents.map(el.ref -> _) --> showAtObserver) + + /** [[Observer]] you can feed a popover ref to close it. */ + val closeObserver: Observer[Ref] = Observer(_.close()) + + /** [[Mod]] for [[Popover]]s closing them each time the stream emits. */ + def closeFromEvents(closeEvents: EventStream[Unit]): Mod[ReactiveHtmlElement[Ref]] = + inContext[ReactiveHtmlElement[Ref]](el => closeEvents.mapTo(el.ref) --> closeObserver) + + /** [[Observer]] you can feed a popover ref to apply focus to it. */ + val applyFocusObserver: Observer[Ref] = Observer(_.applyFocus()) + } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProductSwitch.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProductSwitch.scala index 4a85f63..524eb22 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProductSwitch.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProductSwitch.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-product-switch is an SAP Fiori specific web component that is used in ui5-shellbar and allows the user to * easily switch between products. @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object ProductSwitch { +object ProductSwitch extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -36,8 +37,6 @@ object ProductSwitch { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-product-switch") - val id: ReactiveProp[String, String] = idAttr - object slots {} object events {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProductSwitchItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProductSwitchItem.scala index 5f1e169..e033f30 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProductSwitchItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProductSwitchItem.scala @@ -11,6 +11,8 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent /** The ui5-product-switch-item web component represents the items displayed in the ui5-product-switch web component. * @@ -20,7 +22,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object ProductSwitchItem extends HasIcon { +object ProductSwitchItem extends WebComponent with HasIcon { @js.native trait RawElement extends js.Object {} @@ -37,19 +39,19 @@ object ProductSwitchItem extends HasIcon { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-product-switch-item") - val id: ReactiveProp[String, String] = idAttr + lazy val subtitleText: ReactiveHtmlAttr[String] = customHtmlAttr[String]("subtitle-text", StringAsIsCodec) - val subtitleText: ReactiveHtmlAttr[String] = customHtmlAttr[String]("subtitle-text", StringAsIsCodec) + lazy val target: ReactiveHtmlAttr[LinkTarget] = customHtmlAttr[LinkTarget]("target", LinkTarget.AsStringCodec) - val target: ReactiveHtmlAttr[LinkTarget] = customHtmlAttr[LinkTarget]("target", LinkTarget.AsStringCodec) + lazy val targetSrc: ReactiveHtmlAttr[String] = customHtmlAttr[String]("target-src", StringAsIsCodec) - val targetSrc: ReactiveHtmlAttr[String] = customHtmlAttr[String]("target-src", StringAsIsCodec) - - val titleText: ReactiveHtmlAttr[String] = customHtmlAttr[String]("title-text", StringAsIsCodec) + lazy val titleText: ReactiveHtmlAttr[String] = customHtmlAttr[String]("title-text", StringAsIsCodec) object slots {} - object events extends HasOnClick {} + object events { + val onClick: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("click") + } def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(ProductSwitchItem)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProgressIndicator.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProgressIndicator.scala index f3f2875..85c584f 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProgressIndicator.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ProgressIndicator.scala @@ -12,6 +12,7 @@ import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import com.raquo.domtypes.generic.codecs.IntAsStringCodec import be.doeraene.webcomponents.ui5.configkeys.ValueState +import be.doeraene.webcomponents.WebComponent /** Shows the progress of a process in a graphical way. To indicate the progress, the inside of the component is filled * with a color. @@ -20,7 +21,7 @@ import be.doeraene.webcomponents.ui5.configkeys.ValueState * the doc for more * information. */ -object ProgressIndicator { +object ProgressIndicator extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -37,20 +38,18 @@ object ProgressIndicator { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-progress-indicator") - val id: ReactiveProp[String, String] = idAttr - - val disabled: ReactiveHtmlAttr[Boolean] = + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val displayValue: ReactiveHtmlAttr[String] = + lazy val displayValue: ReactiveHtmlAttr[String] = customHtmlAttr("display-value", StringAsIsCodec) - val hideValue: ReactiveHtmlAttr[Boolean] = + lazy val hideValue: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-value", BooleanAsAttrPresenceCodec) - val value: ReactiveHtmlAttr[Int] = customHtmlAttr("value", IntAsStringCodec) + lazy val value: ReactiveHtmlAttr[Int] = customHtmlAttr("value", IntAsStringCodec) - val valueState: ReactiveHtmlAttr[ValueState] = + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(ProgressIndicator)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RadioButton.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RadioButton.scala index d0af2de..5fb1df4 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RadioButton.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RadioButton.scala @@ -12,6 +12,7 @@ import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.configkeys.ValueState import be.doeraene.webcomponents.ui5.configkeys.WrappingType +import be.doeraene.webcomponents.WebComponent /** The ui5-radio-button component enables users to select a single option from a set of options. When a * ui5-radio-button is selected by the user, the change event is fired. When a ui5-radio-button that is within a group @@ -23,7 +24,7 @@ import be.doeraene.webcomponents.ui5.configkeys.WrappingType * the doc for more * information. */ -object RadioButton extends HasAccessibleName with HasName with HasText { +object RadioButton extends WebComponent with HasAccessibleName with HasName with HasText { @js.native trait RawElement extends js.Object {} @@ -40,15 +41,13 @@ object RadioButton extends HasAccessibleName with HasName with HasText { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-radio-button") - val id: ReactiveProp[String, String] = idAttr + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val checked: ReactiveHtmlAttr[Boolean] = customHtmlAttr("checked", BooleanAsAttrPresenceCodec) + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val checked: ReactiveHtmlAttr[Boolean] = customHtmlAttr("checked", BooleanAsAttrPresenceCodec) - val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) - val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) - - val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) + lazy val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RangeSlider.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RangeSlider.scala new file mode 100644 index 0000000..aa55050 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RangeSlider.scala @@ -0,0 +1,75 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import com.raquo.domtypes.generic.codecs.DoubleAsStringCodec +import com.raquo.domtypes.generic.codecs.IntAsStringCodec +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent + +/** Represents a numerical interval and two handles (grips) to select a sub-range within it. The purpose of the + * component to enable visual selection of sub-ranges within a given interval. + * + * @see + * the doc for more + * information. + */ +object RangeSlider extends WebComponent { + + @js.native + trait RawElement extends js.Object { + def endValue: Double = js.native + def startValue: Double = js.native + } + + @js.native + @JSImport("@ui5/webcomponents/dist/RangeSlider.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = RangeSlider.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-range-slider") + + lazy val endValue: ReactiveHtmlAttr[Double] = customHtmlAttr("end-value", DoubleAsStringCodec) + + lazy val startValue: ReactiveHtmlAttr[Double] = customHtmlAttr("start-value", DoubleAsStringCodec) + + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val labelInterval: ReactiveHtmlAttr[Int] = customHtmlAttr("label-interval", IntAsStringCodec) + + lazy val max: ReactiveHtmlAttr[Double] = customHtmlAttr("max", DoubleAsStringCodec) + + lazy val min: ReactiveHtmlAttr[Double] = customHtmlAttr("min", DoubleAsStringCodec) + + lazy val showTickmarks: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-tickmarks", BooleanAsAttrPresenceCodec) + + lazy val showTooltip: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-tooltip", BooleanAsAttrPresenceCodec) + + lazy val step: ReactiveHtmlAttr[Int] = customHtmlAttr("step", IntAsStringCodec) + + object slots {} + + object events { + val onChange: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("change") + val onInput: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("input") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(RangeSlider)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RatingIndicator.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RatingIndicator.scala index e373446..d29365b 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RatingIndicator.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/RatingIndicator.scala @@ -14,6 +14,7 @@ import scala.scalajs.js.annotation.JSImport import com.raquo.domtypes.generic.codecs.IntAsStringCodec import com.raquo.domtypes.generic.codecs.DoubleAsStringCodec import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent /** The Rating Indicator is used to display a specific number of icons that are used to rate an item. Additionally, it * is also used to display the average and overall ratings. @@ -22,7 +23,7 @@ import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget * the doc for more * information. */ -object RatingIndicator { +object RatingIndicator extends WebComponent { @js.native trait RawElement extends js.Object { @@ -41,13 +42,11 @@ object RatingIndicator { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-rating-indicator") - val id: ReactiveProp[String, String] = idAttr - - val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val max: ReactiveHtmlAttr[Int] = customHtmlAttr("max", IntAsStringCodec) - val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) - val value: ReactiveHtmlAttr[Double] = customHtmlAttr("value", DoubleAsStringCodec) + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val max: ReactiveHtmlAttr[Int] = customHtmlAttr("max", IntAsStringCodec) + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + lazy val value: ReactiveHtmlAttr[Double] = customHtmlAttr("value", DoubleAsStringCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ResponsivePopover.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ResponsivePopover.scala new file mode 100644 index 0000000..2f307b6 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ResponsivePopover.scala @@ -0,0 +1,121 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.* +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.WebComponent + +/** The ui5-responsive-popover acts as a Popover on desktop and tablet, while on phone it acts as a Dialog. The + * component improves tremendously the user experience on mobile. + * + * @see + * the doc for more + * information. + */ +object ResponsivePopover extends WebComponent with HasAccessibleName { + + @js.native + trait RawElement extends js.Object { + def showAt(opener: dom.HTMLElement): Unit = js.native + + def applyFocus(): Unit = js.native + + def close(): Unit = js.native + + def isOpen(): Boolean = js.native + } + + @js.native + @JSImport("@ui5/webcomponents/dist/ResponsivePopover.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = ResponsivePopover.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-responsive-popover") + + lazy val allowTargetOverlap: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("allow-target-overlap", BooleanAsAttrPresenceCodec) + + lazy val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) + + lazy val hideArrow: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-arrow", BooleanAsAttrPresenceCodec) + + lazy val hideBackdrop: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-backdrop", BooleanAsAttrPresenceCodec) + + lazy val horizontalAlign: ReactiveHtmlAttr[PopoverHorizontalAlign] = + customHtmlAttr("horizontal-align", PopoverHorizontalAlign.AsStringCodec) + + lazy val modal: ReactiveHtmlAttr[Boolean] = customHtmlAttr("modal", BooleanAsAttrPresenceCodec) + + /** id of the element that opens the ResponsivePopover */ + lazy val opener: ReactiveHtmlAttr[String] = customHtmlAttr("opener", StringAsIsCodec) + + lazy val placementType: ReactiveHtmlAttr[PopoverPlacementType] = + customHtmlAttr("placement-type", PopoverPlacementType.AsStringCodec) + + lazy val verticalAlign: ReactiveHtmlAttr[PopoverVerticalAlign] = + customHtmlAttr("vertical-align", PopoverVerticalAlign.AsStringCodec) + + lazy val initialFocus: ReactiveHtmlAttr[String] = customHtmlAttr("initial-focus", StringAsIsCodec) + + lazy val open: ReactiveHtmlAttr[Boolean] = customHtmlAttr("open", BooleanAsAttrPresenceCodec) + + lazy val preventFocusRestore: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("prevent-focus-restore", BooleanAsAttrPresenceCodec) + + object slots { + def header: Slot = new Slot("header") + def footer: Slot = new Slot("footer") + } + + object events { + val onAfterClose: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("after-close") + val onAfterOpen: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("after-open") + + trait BeforeCloseInfo extends js.Object { + def escPressed: Boolean + } + + val onBeforeClose: EventProp[EventWithPreciseTarget[Ref] & HasDetail[BeforeCloseInfo]] = new EventProp( + "before-close" + ) + val onBeforeOpen: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("before-open") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(ResponsivePopover)): _*) + + def getResponsivePopoverById(id: String): Option[Ref] = + Option(dom.document.getElementById(id)).map(_.asInstanceOf[dom.HTMLElement & RawElement]) + + /** [[Observer]] you can feed a ResponsivePopover ref and a [[dom.HTMLElement]] to open the ResponsivePopover at the + * element. + */ + val showAtObserver: Observer[(Ref, dom.HTMLElement)] = Observer(_ showAt _) + + def showAtFromEvents(openerEvents: EventStream[dom.HTMLElement]): Mod[ReactiveHtmlElement[Ref]] = + inContext[ReactiveHtmlElement[Ref]](el => openerEvents.map(el.ref -> _) --> showAtObserver) + + /** [[Observer]] you can feed a ResponsivePopover ref to close it. */ + val closeObserver: Observer[Ref] = Observer(_.close()) + + def closeFromEvents(closeEvents: EventStream[Unit]): Mod[ReactiveHtmlElement[Ref]] = + inContext[ReactiveHtmlElement[Ref]](el => closeEvents.mapTo(el.ref) --> closeObserver) + + /** [[Observer]] you can feed a ResponsivePopover ref to apply focus to it. */ + val applyFocusObserver: Observer[Ref] = Observer(_.applyFocus()) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SegmentedButton.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SegmentedButton.scala index 8948306..95918b3 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SegmentedButton.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SegmentedButton.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-segmented-button shows a group of items. When the user clicks or taps one of the items, it stays in a * pressed state. It automatically resizes the items to fit proportionally within the component. When no width is set, @@ -21,7 +22,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object SegmentedButton { +object SegmentedButton extends WebComponent { @js.native trait RawElement extends js.Object { @@ -40,9 +41,7 @@ object SegmentedButton { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-segmented-button") - val id: ReactiveProp[String, String] = idAttr - - val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SegmentedButtonItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SegmentedButtonItem.scala index 2b26cc8..9f4ef43 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SegmentedButtonItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SegmentedButtonItem.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** Users can use the ui5-segmented-button-item as part of a ui5-segmented-button. * @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object SegmentedButtonItem extends HasAccessibleName with HasIcon { +object SegmentedButtonItem extends WebComponent with HasAccessibleName with HasIcon { @js.native trait RawElement extends js.Object {} @@ -36,21 +37,19 @@ object SegmentedButtonItem extends HasAccessibleName with HasIcon { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-segmented-button-item") - val id: ReactiveProp[String, String] = idAttr + lazy val design: ReactiveHtmlAttr[ButtonDesign] = customHtmlAttr("design", ButtonDesign.AsStringCodec) - val design: ReactiveHtmlAttr[ButtonDesign] = customHtmlAttr("design", ButtonDesign.AsStringCodec) + lazy val iconEnd: ReactiveHtmlAttr[Boolean] = customHtmlAttr("icon-end", BooleanAsAttrPresenceCodec) - val iconEnd: ReactiveHtmlAttr[Boolean] = customHtmlAttr("icon-end", BooleanAsAttrPresenceCodec) + lazy val submits: ReactiveHtmlAttr[Boolean] = customHtmlAttr("submits", BooleanAsAttrPresenceCodec) - val submits: ReactiveHtmlAttr[Boolean] = customHtmlAttr("submits", BooleanAsAttrPresenceCodec) - - val pressed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("pressed", BooleanAsAttrPresenceCodec) + lazy val pressed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("pressed", BooleanAsAttrPresenceCodec) lazy val accessibilityAttributes: ReactiveHtmlAttr[js.Object] = ??? // todo - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val tooltip: ReactiveHtmlAttr[String] = customHtmlAttr("tooltip", StringAsIsCodec) + lazy val tooltip: ReactiveHtmlAttr[String] = customHtmlAttr("tooltip", StringAsIsCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Select.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Select.scala index 9cd56e1..5300445 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Select.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Select.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-select component is used to create a drop-down list. The items inside the ui5-select define the available * options by using the ui5-option component. @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object Select extends HasIcon with HasAccessibleName with HasName { +object Select extends WebComponent with HasIcon with HasAccessibleName with HasName { @js.native trait RawElement extends js.Object { @@ -38,12 +39,10 @@ object Select extends HasIcon with HasAccessibleName with HasName { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-select") - val id: ReactiveProp[String, String] = idAttr + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) - - val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) object slots { val valueStateMessage: Slot = new Slot("valueStateMessage") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SelectOption.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SelectOption.scala index 6f97a3f..42bd004 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SelectOption.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SelectOption.scala @@ -9,13 +9,14 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-option component defines the content of an option in the ui5-select. * * @see * the doc for more information. */ -object SelectOption extends HasIcon with HasAdditionalText with HasValue { +object SelectOption extends WebComponent with HasIcon with HasAdditionalText with HasValue { @js.native trait RawElement extends js.Object { @@ -36,10 +37,8 @@ object SelectOption extends HasIcon with HasAdditionalText with HasValue { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-option") - val id: ReactiveProp[String, String] = idAttr - - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(SelectOption)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ShellBar.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ShellBar.scala index e838546..aafd7ec 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ShellBar.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ShellBar.scala @@ -13,6 +13,8 @@ import be.doeraene.webcomponents.ui5.internal.Slot import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.eventtypes.HasItem +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent /** Simple UI button * @@ -20,7 +22,7 @@ import be.doeraene.webcomponents.ui5.eventtypes.HasItem * the doc for more * information. */ -object ShellBar extends HasIcon with HasOnClick { +object ShellBar extends WebComponent with HasIcon { @js.native trait RawElement extends js.Object { @@ -39,34 +41,36 @@ object ShellBar extends HasIcon with HasOnClick { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-shellbar") - val id: ReactiveProp[String, String] = idAttr - - val primaryTitle: ReactiveHtmlAttr[String] = + lazy val primaryTitle: ReactiveHtmlAttr[String] = customHtmlAttr("primary-title", StringAsIsCodec) - val secondaryTitle: ReactiveHtmlAttr[String] = + lazy val secondaryTitle: ReactiveHtmlAttr[String] = customHtmlAttr("secondary-title", StringAsIsCodec) - val notificationsCount: ReactiveHtmlAttr[String] = + lazy val notificationsCount: ReactiveHtmlAttr[String] = customHtmlAttr("notifications-count", StringAsIsCodec) - val showProductSwitch: ReactiveHtmlAttr[Boolean] = + lazy val showProductSwitch: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-product-switch", BooleanAsAttrPresenceCodec) - val showCoPilot: ReactiveHtmlAttr[Boolean] = + lazy val showCoPilot: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-copilot", BooleanAsAttrPresenceCodec) - val showNotifications: ReactiveHtmlAttr[Boolean] = + lazy val showNotifications: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-notifications", BooleanAsAttrPresenceCodec) object events { - val onCopilotClick = new EventProp[dom.Event & HasDetail[HasTargetRef[dom.HTMLElement]]]("co-pilot-click") - val onProfileClick = new EventProp[dom.Event & HasDetail[HasTargetRef[dom.HTMLElement]]]("profile-click") - val onLogoClick = new EventProp[dom.Event & HasDetail[HasTargetRef[dom.HTMLElement]]]("logo-click") - val onMenuItemClick = new EventProp[dom.Event & HasDetail[HasItem[dom.HTMLElement]]]("menu-item-click") + val onCopilotClick = + new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasTargetRef[dom.HTMLElement]]]("co-pilot-click") + val onProfileClick = + new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasTargetRef[dom.HTMLElement]]]("profile-click") + val onLogoClick = + new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasTargetRef[dom.HTMLElement]]]("logo-click") + val onMenuItemClick = + new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[dom.HTMLElement]]]("menu-item-click") val onNotificationsClick = - new EventProp[dom.Event & HasDetail[HasTargetRef[dom.HTMLElement]]]("notifications-click") + new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasTargetRef[dom.HTMLElement]]]("notifications-click") val onProductSwitchClick = - new EventProp[dom.Event & HasDetail[HasTargetRef[dom.HTMLElement]]]("product-switch-click") + new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasTargetRef[dom.HTMLElement]]]("product-switch-click") } object slots { diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ShellBarItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ShellBarItem.scala index dc90a50..b230267 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ShellBarItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ShellBarItem.scala @@ -9,14 +9,18 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.ui5.eventtypes.HasTargetRef +import be.doeraene.webcomponents.WebComponent -/** Simple UI button +/** The ui5-shellbar-item represents a custom item, that might be added to the ui5-shellbar. * * @see * the doc for more * information. */ -object ShellBarItem extends HasIcon with HasOnClick with HasText { +object ShellBarItem extends WebComponent with HasIcon with HasText { @js.native trait RawElement extends js.Object {} @@ -33,9 +37,14 @@ object ShellBarItem extends HasIcon with HasOnClick with HasText { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-shellbar-item") - val id: ReactiveProp[String, String] = idAttr + lazy val count: HtmlAttr[String] = customHtmlAttr("count", StringAsIsCodec) - val count: HtmlAttr[String] = customHtmlAttr("count", StringAsIsCodec) + object slots {} + + object events { + val onClick: EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasTargetRef[dom.HTMLElement]]] = + new EventProp("click") + } def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(ShellBarItem)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigation.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigation.scala index 3bb8be1..7ae5042 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigation.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigation.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The SideNavigation is used as a standard menu in applications. It consists of three containers: header * (top-aligned), main navigation section (top-aligned) and the secondary section (bottom-aligned). @@ -20,7 +21,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object SideNavigation { +object SideNavigation extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -37,9 +38,7 @@ object SideNavigation { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-side-navigation") - val id: ReactiveProp[String, String] = idAttr - - val collapsed: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("collapsed", BooleanAsAttrPresenceCodec) + lazy val collapsed: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("collapsed", BooleanAsAttrPresenceCodec) object slots { val fixedItems: Slot = new Slot("fixedItems") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigationItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigationItem.scala index f12c741..ea6ddc7 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigationItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigationItem.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-side-navigation-item is used within ui5-side-navigation only. Via the ui5-side-navigation-item you control * the content of the SideNavigation. @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object SideNavigationItem extends HasText { +object SideNavigationItem extends WebComponent with HasText { @js.native trait RawElement extends SideNavigation.events.SideNavigationItemRawElement {} @@ -36,15 +37,13 @@ object SideNavigationItem extends HasText { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-side-navigation-item") - val id: ReactiveProp[String, String] = idAttr + lazy val expanded: ReactiveHtmlAttr[Boolean] = customHtmlAttr("expanded", BooleanAsAttrPresenceCodec) - val expanded: ReactiveHtmlAttr[Boolean] = customHtmlAttr("expanded", BooleanAsAttrPresenceCodec) + lazy val icon: ReactiveHtmlAttr[IconName] = customHtmlAttr("icon", IconName.AsStringCodec) - val icon: ReactiveHtmlAttr[IconName] = customHtmlAttr("icon", IconName.AsStringCodec) + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) - val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) - - val wholeItemToggleable: ReactiveHtmlAttr[Boolean] = + lazy val wholeItemToggleable: ReactiveHtmlAttr[Boolean] = customHtmlAttr("whole-item-toggleable", BooleanAsAttrPresenceCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigationSubItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigationSubItem.scala index a97f179..c7b88c3 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigationSubItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SideNavigationSubItem.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** The ui5-side-navigation-item is used within ui5-side-navigation only. Via the ui5-side-navigation-item you control * the content of the SideNavigation. @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for * more information. */ -object SideNavigationSubItem extends HasText { +object SideNavigationSubItem extends WebComponent with HasText { @js.native trait RawElement extends SideNavigation.events.SideNavigationItemRawElement {} @@ -36,11 +37,9 @@ object SideNavigationSubItem extends HasText { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-side-navigation-sub-item") - val id: ReactiveProp[String, String] = idAttr + lazy val icon: ReactiveHtmlAttr[IconName] = customHtmlAttr("icon", IconName.AsStringCodec) - val icon: ReactiveHtmlAttr[IconName] = customHtmlAttr("icon", IconName.AsStringCodec) - - val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Slider.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Slider.scala new file mode 100644 index 0000000..327c672 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Slider.scala @@ -0,0 +1,71 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import com.raquo.domtypes.generic.codecs.DoubleAsStringCodec +import com.raquo.domtypes.generic.codecs.IntAsStringCodec +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent + +/** The Slider component represents a numerical range and a handle (grip). The purpose of the component is to enable + * visual selection of a value in a continuous numerical range by moving an adjustable handle. + * + * @see + * the doc for more information. + */ +object Slider extends WebComponent { + + @js.native + trait RawElement extends js.Object { + def value: Double = js.native + } + + @js.native + @JSImport("@ui5/webcomponents/dist/Slider.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = Slider.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-slider") + + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val labelInterval: ReactiveHtmlAttr[Int] = customHtmlAttr("label-interval", IntAsStringCodec) + + lazy val max: ReactiveHtmlAttr[Double] = customHtmlAttr("max", DoubleAsStringCodec) + + lazy val min: ReactiveHtmlAttr[Double] = customHtmlAttr("min", DoubleAsStringCodec) + + lazy val showTickmarks: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-tickmarks", BooleanAsAttrPresenceCodec) + + lazy val showTooltip: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-tooltip", BooleanAsAttrPresenceCodec) + + lazy val step: ReactiveHtmlAttr[Int] = customHtmlAttr("step", IntAsStringCodec) + + lazy val value: ReactiveHtmlAttr[Double] = customHtmlAttr("value", DoubleAsStringCodec) + + object slots {} + + object events { + val onChange: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("change") + val onInput: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("input") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Slider)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SortItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SortItem.scala new file mode 100644 index 0000000..7b4eb79 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SortItem.scala @@ -0,0 +1,46 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent + +/** For the ui5-sort-item + * + * @see + * the doc for more + * information. + */ +object SortItem extends WebComponent with HasText { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/SortItem.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = SortItem.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-sort-item") + + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + + object slots {} + + object events {} + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(SortItem)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SplitButton.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SplitButton.scala new file mode 100644 index 0000000..f8f0695 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SplitButton.scala @@ -0,0 +1,59 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent + +/** ui5-split-button enables users to trigger actions. It is constructed of two separate actions - default action and + * arrow action that can be activated by clicking or tapping, or by pressing certain keyboard keys - Space or Enter for + * default action, and Arrow Down or Arrow Up for arrow action. + * + * @see + * the doc for more + * information. + */ +object SplitButton extends WebComponent with HasIcon { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents/dist/SplitButton.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = SplitButton.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-split-button") + + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) + + lazy val activeIcon: ReactiveHtmlAttr[IconName] = customHtmlAttr("active-icon", IconName.AsStringCodec) + + lazy val design: ReactiveHtmlAttr[ButtonDesign] = customHtmlAttr("design", ButtonDesign.AsStringCodec) + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + object slots {} + + object events { + val onArrowClick: EventProp[EventWithPreciseTarget[dom.HTMLElement]] = new EventProp("arrow-click") + val onClick: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("click") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(SplitButton)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/StepInput.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/StepInput.scala new file mode 100644 index 0000000..3432051 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/StepInput.scala @@ -0,0 +1,80 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import com.raquo.domtypes.generic.codecs.DoubleAsStringCodec +import com.raquo.domtypes.generic.codecs.IntAsStringCodec +import be.doeraene.webcomponents.ui5.configkeys.ValueState +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent + +/** The ui5-step-input consists of an input field and buttons with icons to increase/decrease the value with the + * predefined step. + * + * The user can change the value of the component by pressing the increase/decrease buttons, by typing a number + * directly, by using the keyboard up/down and page up/down, or by using the mouse scroll wheel. Decimal values are + * supported. + * + * @see + * the doc for more + * information. + */ +object StepInput extends WebComponent with HasAccessibleName with HasName { + + @js.native + trait RawElement extends js.Object { + def value: Double = js.native + } + + @js.native + @JSImport("@ui5/webcomponents/dist/StepInput.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = StepInput.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-step-input") + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val max: ReactiveHtmlAttr[Double] = customHtmlAttr("max", DoubleAsStringCodec) + + lazy val min: ReactiveHtmlAttr[Double] = customHtmlAttr("min", DoubleAsStringCodec) + + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) + + lazy val step: ReactiveHtmlAttr[Double] = customHtmlAttr("step", DoubleAsStringCodec) + + lazy val value: ReactiveHtmlAttr[Double] = customHtmlAttr("value", DoubleAsStringCodec) + + lazy val valuePrecision: ReactiveHtmlAttr[Int] = customHtmlAttr("value-precision", IntAsStringCodec) + + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + + object slots { + val valueStateMessage: Slot = Slot("valueStateMessage") + } + + object events { + val onChange: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("change") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(StepInput)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SuggestionGroupItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SuggestionGroupItem.scala new file mode 100644 index 0000000..6c49052 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SuggestionGroupItem.scala @@ -0,0 +1,44 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent + +/** The ui5-SuggestionGroupItem + * + * @see + * the doc for more information. + */ +object SuggestionGroupItem extends WebComponent with HasText { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents/dist/features/InputSuggestions.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = SuggestionGroupItem.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-ui5-suggestion-group-item") + + object slots {} + + object events {} + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(SuggestionGroupItem)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SuggestionItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SuggestionItem.scala index 8a3771a..3ad593e 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SuggestionItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/SuggestionItem.scala @@ -11,13 +11,14 @@ import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.configkeys.ListItemType import be.doeraene.webcomponents.ui5.configkeys.ValueState +import be.doeraene.webcomponents.WebComponent /** The ui5-suggestion-item represents the suggestion item of the ui5-input. * * @see * the doc for more information. */ -object SuggestionItem extends HasIcon with HasDescription with HasText with HasAdditionalText { +object SuggestionItem extends WebComponent with HasIcon with HasDescription with HasText with HasAdditionalText { @js.native trait RawElement extends js.Object { @@ -36,15 +37,13 @@ object SuggestionItem extends HasIcon with HasDescription with HasText with HasA private val tag: HtmlTag[Ref] = customHtmlTag("ui5-suggestion-item") - val id: ReactiveProp[String, String] = idAttr + lazy val iconEnd: ReactiveHtmlAttr[Boolean] = customHtmlAttr("icon-end", BooleanAsAttrPresenceCodec) - val iconEnd: ReactiveHtmlAttr[Boolean] = customHtmlAttr("icon-end", BooleanAsAttrPresenceCodec) + lazy val image: ReactiveHtmlAttr[String] = customHtmlAttr("image", StringAsIsCodec) - val image: ReactiveHtmlAttr[String] = customHtmlAttr("image", StringAsIsCodec) + lazy val tpe: ReactiveHtmlAttr[ListItemType] = customHtmlAttr("tpe", ListItemType.AsStringCodec) - val tpe: ReactiveHtmlAttr[ListItemType] = customHtmlAttr("tpe", ListItemType.AsStringCodec) - - val additionalTextState: ReactiveHtmlAttr[ValueState] = + lazy val additionalTextState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("additional-text-state", ValueState.AsStringCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(SuggestionItem)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Switch.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Switch.scala index 55434d8..7431889 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Switch.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Switch.scala @@ -11,16 +11,20 @@ import org.scalajs.dom.Event import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent /** The ui5-switch component is used for changing between binary states. * * @see * the doc for more information. */ -object Switch extends HasAccessibleName { +object Switch extends WebComponent with HasAccessibleName { @js.native - trait RawElement extends js.Object {} + trait RawElement extends js.Object { + def checked: Boolean = js.native + } @js.native @JSImport("@ui5/webcomponents/dist/Switch.js", JSImport.Default) @@ -34,20 +38,19 @@ object Switch extends HasAccessibleName { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-switch") - val id: ReactiveProp[String, String] = idAttr - - val textOn: ReactiveHtmlAttr[String] = customHtmlAttr("text-on", StringAsIsCodec) - val textOff: ReactiveHtmlAttr[String] = customHtmlAttr("text-off", StringAsIsCodec) + lazy val textOn: ReactiveHtmlAttr[String] = customHtmlAttr("text-on", StringAsIsCodec) + lazy val textOff: ReactiveHtmlAttr[String] = customHtmlAttr("text-off", StringAsIsCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val checked: ReactiveHtmlAttr[Boolean] = customHtmlAttr("checked", BooleanAsAttrPresenceCodec) + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val checked: ReactiveHtmlAttr[Boolean] = customHtmlAttr("checked", BooleanAsAttrPresenceCodec) - val design: ReactiveHtmlAttr[SwitchDesign] = customHtmlAttr("design", SwitchDesign.AsStringCodec) + lazy val design: ReactiveHtmlAttr[SwitchDesign] = customHtmlAttr("design", SwitchDesign.AsStringCodec) object slots {} - object events extends HasOnChange { - val onCheckedChange: EventProcessor[Event, Boolean] = onChange.mapToChecked + object events { + val onChange: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("change") + val onCheckedChange: EventProcessor[EventWithPreciseTarget[Ref], Boolean] = onChange.map(_.target.checked) } def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Switch)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Tab.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Tab.scala index 68180a6..237bd49 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Tab.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Tab.scala @@ -11,6 +11,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** Element contained in a [[TabContainer]]. * @@ -18,7 +19,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object Tab extends HasIcon with HasText { +object Tab extends WebComponent with HasIcon with HasText { @js.native trait RawElement extends js.Object { @@ -37,14 +38,12 @@ object Tab extends HasIcon with HasText { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-tab") - val id: ReactiveProp[String, String] = idAttr + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + lazy val design: ReactiveHtmlAttr[SemanticColour] = customHtmlAttr("design", SemanticColour.AsStringCodec) - val design: ReactiveHtmlAttr[SemanticColour] = customHtmlAttr("design", SemanticColour.AsStringCodec) - - val additionalText: ReactiveHtmlAttr[String] = customHtmlAttr("additional-text", StringAsIsCodec) + lazy val additionalText: ReactiveHtmlAttr[String] = customHtmlAttr("additional-text", StringAsIsCodec) object slots { val subTabs: Slot = new Slot("subTabs") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TabContainer.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TabContainer.scala index ab4d7bf..89b1d25 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TabContainer.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TabContainer.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** Tab container * @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object TabContainer { +object TabContainer extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -36,14 +37,12 @@ object TabContainer { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-tabcontainer") - val id: ReactiveProp[String, String] = idAttr - - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val collapsed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("collapsed", BooleanAsAttrPresenceCodec) - val fixed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("fixed", BooleanAsAttrPresenceCodec) - val showOverflow: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-overflow", BooleanAsAttrPresenceCodec) - val tabLayout: ReactiveHtmlAttr[TabLayout] = customHtmlAttr("tab-layout", TabLayout.AsStringCodec) - val tabsOverflowMode: ReactiveHtmlAttr[TabsOverflowMode] = + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val collapsed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("collapsed", BooleanAsAttrPresenceCodec) + lazy val fixed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("fixed", BooleanAsAttrPresenceCodec) + lazy val showOverflow: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-overflow", BooleanAsAttrPresenceCodec) + lazy val tabLayout: ReactiveHtmlAttr[TabLayout] = customHtmlAttr("tab-layout", TabLayout.AsStringCodec) + lazy val tabsOverflowMode: ReactiveHtmlAttr[TabsOverflowMode] = customHtmlAttr("tabs-overflow-mode", TabsOverflowMode.AsStringCodec) object slots { diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Table.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Table.scala index 5eb531c..aec60dd 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Table.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Table.scala @@ -15,8 +15,9 @@ import scala.compiletime.ops.int.<= import scala.concurrent.duration.FiniteDuration import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent -object Table { +object Table extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -33,17 +34,16 @@ object Table { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-table") - val id: ReactiveProp[String, String] = idAttr - - val busy: ReactiveHtmlAttr[Boolean] = customHtmlAttr("busy", BooleanAsAttrPresenceCodec) - val busyDelay: ReactiveHtmlAttr[FiniteDuration] = customHtmlAttr("busy-delay", FiniteDurationCodec) - val growing: ReactiveHtmlAttr[TableGrowingMode] = customHtmlAttr("growing", TableGrowingMode.AsStringCodec) - val growingButtonSubtext: ReactiveHtmlAttr[String] = customHtmlAttr("growing-button-subtext", StringAsIsCodec) - val growingButtonText: ReactiveHtmlAttr[String] = customHtmlAttr("growing-button-text", StringAsIsCodec) - val hideNoData: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-no-data", BooleanAsAttrPresenceCodec) - val mode: ReactiveHtmlAttr[TableMode] = customHtmlAttr("mode", TableMode.AsStringCodec) - val noDataText: ReactiveHtmlAttr[String] = customHtmlAttr("no-data-text", StringAsIsCodec) - val stickyColumnHeader: ReactiveHtmlAttr[Boolean] = customHtmlAttr("sticky-column-header", BooleanAsAttrPresenceCodec) + lazy val busy: ReactiveHtmlAttr[Boolean] = customHtmlAttr("busy", BooleanAsAttrPresenceCodec) + lazy val busyDelay: ReactiveHtmlAttr[FiniteDuration] = customHtmlAttr("busy-delay", FiniteDurationCodec) + lazy val growing: ReactiveHtmlAttr[TableGrowingMode] = customHtmlAttr("growing", TableGrowingMode.AsStringCodec) + lazy val growingButtonSubtext: ReactiveHtmlAttr[String] = customHtmlAttr("growing-button-subtext", StringAsIsCodec) + lazy val growingButtonText: ReactiveHtmlAttr[String] = customHtmlAttr("growing-button-text", StringAsIsCodec) + lazy val hideNoData: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-no-data", BooleanAsAttrPresenceCodec) + lazy val mode: ReactiveHtmlAttr[TableMode] = customHtmlAttr("mode", TableMode.AsStringCodec) + lazy val noDataText: ReactiveHtmlAttr[String] = customHtmlAttr("no-data-text", StringAsIsCodec) + lazy val stickyColumnHeader: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("sticky-column-header", BooleanAsAttrPresenceCodec) object slots { val columns: Slot = new Slot("columns") diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableCell.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableCell.scala index e7ac6b3..2f89bf7 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableCell.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableCell.scala @@ -7,8 +7,9 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent -object TableCell { +object TableCell extends WebComponent { @js.native trait RawElement extends js.Object {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableColumn.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableColumn.scala index 7fb3eb8..1ec5dde 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableColumn.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableColumn.scala @@ -12,8 +12,9 @@ import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import com.raquo.domtypes.generic.codecs.IntAsStringCodec import com.raquo.domtypes.generic.codecs.BooleanAsAttrPresenceCodec +import be.doeraene.webcomponents.WebComponent -object TableColumn { +object TableColumn extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -30,12 +31,9 @@ object TableColumn { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-table-column") - val demandPopin: ReactiveHtmlAttr[Boolean] = customHtmlAttr("demand-popin", BooleanAsAttrPresenceCodec) - val minWidth: ReactiveHtmlAttr[Int] = customHtmlAttr("min-width", IntAsStringCodec) - val popinText: ReactiveHtmlAttr[String] = customHtmlAttr("popin-text", StringAsIsCodec) - - val slot: ReactiveHtmlAttr["columns" | "default"] = - customHtmlAttr("slot", EmbeddingAsIsCodec.apply) + lazy val demandPopin: ReactiveHtmlAttr[Boolean] = customHtmlAttr("demand-popin", BooleanAsAttrPresenceCodec) + lazy val minWidth: ReactiveHtmlAttr[Int] = customHtmlAttr("min-width", IntAsStringCodec) + lazy val popinText: ReactiveHtmlAttr[String] = customHtmlAttr("popin-text", StringAsIsCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(TableColumn)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableRow.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableRow.scala index f4d612e..26f108f 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableRow.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TableRow.scala @@ -7,8 +7,9 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent -object TableRow { +object TableRow extends WebComponent { @js.native trait RawElement extends js.Object {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TextArea.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TextArea.scala index 6c7358f..fe396b7 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TextArea.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TextArea.scala @@ -13,14 +13,15 @@ import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget import be.doeraene.webcomponents.ui5.configkeys.ValueState import com.raquo.domtypes.generic.codecs.IntAsStringCodec import be.doeraene.webcomponents.ui5.internal.Slot +import be.doeraene.webcomponents.WebComponent -/** TextArea +/** The ui5-textarea component is used to enter multiple lines of text. * * @see * the doc for more * information. */ -object TextArea extends HasValue with HasAccessibleName with HasName { +object TextArea extends WebComponent with HasValue with HasAccessibleName with HasName { @js.native trait RawElement extends js.Object {} @@ -37,18 +38,17 @@ object TextArea extends HasValue with HasAccessibleName with HasName { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-textarea") - val id: ReactiveProp[String, String] = idAttr - - val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) - val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) - val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) - val growing: ReactiveHtmlAttr[Boolean] = customHtmlAttr("growing", BooleanAsAttrPresenceCodec) - val showExceededText: ReactiveHtmlAttr[Boolean] = customHtmlAttr("show-exceeded-text", BooleanAsAttrPresenceCodec) - val growingMaxLines: ReactiveHtmlAttr[Int] = customHtmlAttr("growing-max-lines", IntAsStringCodec) - val maxLength: ReactiveHtmlAttr[Int] = customHtmlAttr("maxlength", IntAsStringCodec) - val rows: ReactiveHtmlAttr[Int] = customHtmlAttr("rows", IntAsStringCodec) - val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) - val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + lazy val required: ReactiveHtmlAttr[Boolean] = customHtmlAttr("required", BooleanAsAttrPresenceCodec) + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + lazy val growing: ReactiveHtmlAttr[Boolean] = customHtmlAttr("growing", BooleanAsAttrPresenceCodec) + lazy val showExceededText: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("show-exceeded-text", BooleanAsAttrPresenceCodec) + lazy val growingMaxLines: ReactiveHtmlAttr[Int] = customHtmlAttr("growing-max-lines", IntAsStringCodec) + lazy val maxLength: ReactiveHtmlAttr[Int] = customHtmlAttr("maxlength", IntAsStringCodec) + lazy val rows: ReactiveHtmlAttr[Int] = customHtmlAttr("rows", IntAsStringCodec) + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) val isRequired: Setter[HtmlElement] = required := true diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TimePicker.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TimePicker.scala new file mode 100644 index 0000000..cc984d0 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TimePicker.scala @@ -0,0 +1,78 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.configkeys.ValueState +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent + +/** The ui5-time-picker component provides an input field with assigned sliders which are opened on user action. The + * ui5-time-picker allows users to select a localized time using touch, mouse, or keyboard input. It consists of two + * parts: the time input field and the sliders. + * + * @see + * the doc for more + * information. + */ +object TimePicker extends WebComponent with HasValue { + + @js.native + trait RawElement extends js.Object { + def dateValue: js.Date = js.native + + def value: String = js.native + + def closePicker(): Unit = js.native + + def formatValue(date: js.Date): String = js.native + + def isOpen(): Boolean = js.native + + def isValid(value: String): Boolean = js.native + + def openPicker(): Unit = js.native + } + + @js.native + @JSImport("@ui5/webcomponents/dist/TimePicker.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = TimePicker.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-time-picker") + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val formatPattern: ReactiveHtmlAttr[String] = customHtmlAttr("format-pattern", StringAsIsCodec) + + lazy val placeholder: ReactiveHtmlAttr[String] = customHtmlAttr("placeholder", StringAsIsCodec) + + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + + lazy val valueState: ReactiveHtmlAttr[ValueState] = customHtmlAttr("value-state", ValueState.AsStringCodec) + + object slots { + val valueStateMessage: Slot = Slot("valueStateMessage") + } + + object events { + val onChange: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("change") + val onInput: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("input") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(TimePicker)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Timeline.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Timeline.scala index f38bf92..e7921e6 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Timeline.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Timeline.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent /** The ui5-timeline component shows entries (such as objects, events, or posts) in chronological order. A common use * case is to provide information about changes to an object, or events related to an object. These entries can be @@ -23,7 +24,7 @@ import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget * the doc for more * information. */ -object Timeline { +object Timeline extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -40,11 +41,9 @@ object Timeline { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-timeline") - val id: ReactiveProp[String, String] = idAttr + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) - val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) - - val layout: ReactiveHtmlAttr[TimelineLayout] = customHtmlAttr("layout", TimelineLayout.AsStringCodec) + lazy val layout: ReactiveHtmlAttr[TimelineLayout] = customHtmlAttr("layout", TimelineLayout.AsStringCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TimelineItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TimelineItem.scala index 386fc2a..729a76c 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TimelineItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TimelineItem.scala @@ -12,6 +12,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** An entry posted on the timeline. * @@ -19,7 +20,7 @@ import scala.scalajs.js.annotation.JSImport * the doc for more * information. */ -object TimelineItem extends HasIcon with HasName { +object TimelineItem extends WebComponent with HasIcon with HasName { @js.native trait RawElement extends js.Object { @@ -44,13 +45,11 @@ object TimelineItem extends HasIcon with HasName { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-timeline-item") - val id: ReactiveProp[String, String] = idAttr + lazy val nameClickable: ReactiveHtmlAttr[Boolean] = customHtmlAttr("name-clickable", BooleanAsAttrPresenceCodec) - val nameClickable: ReactiveHtmlAttr[Boolean] = customHtmlAttr("name-clickable", BooleanAsAttrPresenceCodec) + lazy val subtitleText: ReactiveHtmlAttr[String] = customHtmlAttr("subtitle-text", StringAsIsCodec) - val subtitleText: ReactiveHtmlAttr[String] = customHtmlAttr("subtitle-text", StringAsIsCodec) - - val titleText: ReactiveHtmlAttr[String] = customHtmlAttr("title-text", StringAsIsCodec) + lazy val titleText: ReactiveHtmlAttr[String] = customHtmlAttr("title-text", StringAsIsCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Title.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Title.scala index 34b2029..1a523e7 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Title.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Title.scala @@ -13,6 +13,7 @@ import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.configkeys.TitleLevel import be.doeraene.webcomponents.ui5.configkeys.WrappingType +import be.doeraene.webcomponents.WebComponent /** The ui5-title component is used to display titles inside a page. It is a simple, large-sized text with explicit * header/title semantics. @@ -20,7 +21,7 @@ import be.doeraene.webcomponents.ui5.configkeys.WrappingType * @see * the doc for more information. */ -object Title { +object Title extends WebComponent { @js.native trait RawElement extends js.Object {} @@ -37,10 +38,8 @@ object Title { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-title") - val id: ReactiveProp[String, String] = idAttr - - val level: ReactiveHtmlAttr[TitleLevel] = customHtmlAttr("level", TitleLevel.AsStringCodec) - val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) + lazy val level: ReactiveHtmlAttr[TitleLevel] = customHtmlAttr("level", TitleLevel.AsStringCodec) + lazy val wrappingType: ReactiveHtmlAttr[WrappingType] = customHtmlAttr("wrapping-type", WrappingType.AsStringCodec) object slots {} @@ -48,4 +47,22 @@ object Title { def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Title)): _*) + /** Creates Title of H1 level. */ + def h1(mods: ModFunction*): HtmlElement = apply(mods :+ (_.level := TitleLevel.H1): _*) + + /** Creates Title of H2 level. */ + def h2(mods: ModFunction*): HtmlElement = apply(mods :+ (_.level := TitleLevel.H2): _*) + + /** Creates Title of H3 level. */ + def h3(mods: ModFunction*): HtmlElement = apply(mods :+ (_.level := TitleLevel.H3): _*) + + /** Creates Title of H4 level. */ + def h4(mods: ModFunction*): HtmlElement = apply(mods :+ (_.level := TitleLevel.H4): _*) + + /** Creates Title of H4 level. */ + def h5(mods: ModFunction*): HtmlElement = apply(mods :+ (_.level := TitleLevel.H5): _*) + + /** Creates Title of H6 level. */ + def h6(mods: ModFunction*): HtmlElement = apply(mods :+ (_.level := TitleLevel.H6): _*) + } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Toast.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Toast.scala index 6547cff..c20d6c2 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Toast.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Toast.scala @@ -11,13 +11,14 @@ import org.scalajs.dom import scala.concurrent.duration.FiniteDuration import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** Simple UI button * * @see * the doc for more information. */ -object Toast extends HasIcon with HasOnClick { +object Toast extends WebComponent with HasIcon { @js.native trait RawElement extends js.Object { @@ -36,11 +37,13 @@ object Toast extends HasIcon with HasOnClick { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-toast") - val id: ReactiveProp[String, String] = idAttr + lazy val placement: ReactiveHtmlAttr[ToastPlacement] = customHtmlAttr("placement", ToastPlacement.AsStringCodec) - val placement: ReactiveHtmlAttr[ToastPlacement] = customHtmlAttr("placement", ToastPlacement.AsStringCodec) + lazy val duration: ReactiveHtmlAttr[FiniteDuration] = customHtmlAttr("duration", FiniteDurationCodec) - val duration: ReactiveHtmlAttr[FiniteDuration] = customHtmlAttr("duration", FiniteDurationCodec) + object slots {} + + object events {} def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Toast)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ToggleButton.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ToggleButton.scala new file mode 100644 index 0000000..20fdabe --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ToggleButton.scala @@ -0,0 +1,69 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent + +/** The ui5-toggle-button component is an enhanced ui5-button that can be toggled between pressed and normal states. + * Users can use the ui5-toggle-button as a switch to turn a setting on or off. It can also be used to represent an + * independent choice similar to a check box. + * + * @see + * the doc for more + * information. + */ +object ToggleButton extends WebComponent with HasAccessibleName with HasIcon { + + @js.native + trait RawElement extends js.Object { + var accessibilityAttributes: js.Object = js.native + } + + @js.native + @JSImport("@ui5/webcomponents/dist/ToggleButton.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = ToggleButton.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-toggle-button") + + lazy val pressed: ReactiveHtmlAttr[Boolean] = customHtmlAttr("pressed", BooleanAsAttrPresenceCodec) + + // This component has accessibilityAttributes but I currently don't know how to implement it + + lazy val design: ReactiveHtmlAttr[ButtonDesign] = customHtmlAttr("design", ButtonDesign.AsStringCodec) + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val iconEnd: ReactiveHtmlAttr[Boolean] = customHtmlAttr("icon-end", BooleanAsAttrPresenceCodec) + + lazy val submits: ReactiveHtmlAttr[Boolean] = { + SubmitsSupport + customHtmlAttr("submits", BooleanAsAttrPresenceCodec) + } + + lazy val tooltip: ReactiveHtmlAttr[String] = customHtmlAttr("tooltip", StringAsIsCodec) + + object slots {} + + object events { + val onClick: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("click") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(ToggleButton)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Token.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Token.scala new file mode 100644 index 0000000..fd1f3ef --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Token.scala @@ -0,0 +1,57 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.WebComponent + +/** Tokens are small items of information (similar to tags) that mainly serve to visualize previously selected items. + * + * @see + * the doc for more information. + */ +object Token extends WebComponent with HasText { + + @js.native + trait RawElement extends js.Object { + def selected: Boolean = js.native + + def text: String = js.native + } + + @js.native + @JSImport("@ui5/webcomponents/dist/Token.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = Token.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-token") + + lazy val readonly: ReactiveHtmlAttr[Boolean] = customHtmlAttr("readonly", BooleanAsAttrPresenceCodec) + + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + + object slots { + val closeIcon: Slot = new Slot("closeIcon") + } + + object events { + val onSelect: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("select") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Token)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Tree.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Tree.scala index 0e2c007..300ea09 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Tree.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Tree.scala @@ -14,13 +14,14 @@ import scala.scalajs.js.annotation.{JSImport, JSName} import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget import be.doeraene.webcomponents.ui5.eventtypes.HasDetail import be.doeraene.webcomponents.ui5.eventtypes.HasItem +import be.doeraene.webcomponents.WebComponent /** The ui5-tree component provides a tree structure for displaying data in a hierarchy. * * @see * the doc for more information. */ -object Tree { +object Tree extends WebComponent { //noinspection ScalaUnusedSymbol @js.native @@ -40,13 +41,11 @@ object Tree { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-tree") - val id: ReactiveProp[String, String] = idAttr + lazy val footerText: ReactiveHtmlAttr[String] = customHtmlAttr("footer-text", StringAsIsCodec) + lazy val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) + lazy val noDataText: ReactiveHtmlAttr[String] = customHtmlAttr("no-data-text", StringAsIsCodec) - val footerText: ReactiveHtmlAttr[String] = customHtmlAttr("footer-text", StringAsIsCodec) - val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) - val noDataText: ReactiveHtmlAttr[String] = customHtmlAttr("no-data-text", StringAsIsCodec) - - val mode: ReactiveHtmlAttr[ListMode] = customHtmlAttr("mode", ListMode.AsStringCodec) + lazy val mode: ReactiveHtmlAttr[ListMode] = customHtmlAttr("mode", ListMode.AsStringCodec) object events { val onItemClick: EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[TreeItem.Ref]]] = new EventProp( diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TreeItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TreeItem.scala index 43faa5f..063fcde 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TreeItem.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/TreeItem.scala @@ -10,6 +10,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent /** This is the item to use inside a ui5-tree. You can represent an arbitrary tree structure by recursively nesting tree * items. @@ -17,7 +18,7 @@ import scala.scalajs.js.annotation.JSImport * @see * the doc for more information. */ -object TreeItem extends HasIcon with HasText { +object TreeItem extends WebComponent with HasIcon with HasText { @js.native trait RawElement extends js.Object { @@ -36,12 +37,10 @@ object TreeItem extends HasIcon with HasText { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-tree-item") - val id: ReactiveProp[String, String] = idAttr - - val expanded: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("expanded", BooleanAsAttrPresenceCodec) - val hasChildren: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("has-children", BooleanAsAttrPresenceCodec) - val intermediate: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("intermediate", BooleanAsAttrPresenceCodec) - val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("selected", BooleanAsAttrPresenceCodec) + lazy val expanded: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("expanded", BooleanAsAttrPresenceCodec) + lazy val hasChildren: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("has-children", BooleanAsAttrPresenceCodec) + lazy val intermediate: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("intermediate", BooleanAsAttrPresenceCodec) + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr[Boolean]("selected", BooleanAsAttrPresenceCodec) def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(TreeItem)): _*) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UList.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UList.scala index c8a1f2c..256c1cf 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UList.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UList.scala @@ -13,6 +13,7 @@ import org.scalajs.dom import scala.scalajs.js import scala.scalajs.js.annotation.{JSImport, JSName} import scala.concurrent.duration.FiniteDuration +import be.doeraene.webcomponents.WebComponent /** The ui5-list component allows displaying a list of items, advanced keyboard handling support for navigating between * items, and predefined modes to improve the development efficiency. @@ -20,7 +21,7 @@ import scala.concurrent.duration.FiniteDuration * @see * the doc for more information. */ -object UList { +object UList extends WebComponent with HasAccessibleName { @js.native trait RawElement extends js.Object {} @@ -37,44 +38,43 @@ object UList { private val tag: HtmlTag[Ref] = customHtmlTag("ui5-list") - val id: ReactiveProp[String, String] = idAttr - - val busy: ReactiveHtmlAttr[Boolean] = customHtmlAttr("busy", BooleanAsAttrPresenceCodec) - val busyDelay: ReactiveHtmlAttr[FiniteDuration] = customHtmlAttr("busy-delay", FiniteDurationCodec) - val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) - val footerText: ReactiveHtmlAttr[String] = customHtmlAttr("footer-text", StringAsIsCodec) - val mode: ReactiveHtmlAttr[ListMode] = customHtmlAttr("mode", ListMode.AsStringCodec) - val separators: ReactiveHtmlAttr[ListSeparator] = customHtmlAttr("separators", ListSeparator.AsStringCodec) - val noDataText: ReactiveHtmlAttr[String] = customHtmlAttr("no-data-text", StringAsIsCodec) - val growing: ReactiveHtmlAttr[ListGrowingMode] = customHtmlAttr("growing", ListGrowingMode.AsStringCodec) - val indent: ReactiveHtmlAttr[Boolean] = customHtmlAttr("indent", BooleanAsAttrPresenceCodec) + lazy val accessibleRole: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-role", StringAsIsCodec) + lazy val busy: ReactiveHtmlAttr[Boolean] = customHtmlAttr("busy", BooleanAsAttrPresenceCodec) + lazy val busyDelay: ReactiveHtmlAttr[FiniteDuration] = customHtmlAttr("busy-delay", FiniteDurationCodec) + lazy val footerText: ReactiveHtmlAttr[String] = customHtmlAttr("footer-text", StringAsIsCodec) + lazy val growing: ReactiveHtmlAttr[ListGrowingMode] = customHtmlAttr("growing", ListGrowingMode.AsStringCodec) + lazy val headerText: ReactiveHtmlAttr[String] = customHtmlAttr("header-text", StringAsIsCodec) + lazy val indent: ReactiveHtmlAttr[Boolean] = customHtmlAttr("indent", BooleanAsAttrPresenceCodec) + lazy val mode: ReactiveHtmlAttr[ListMode] = customHtmlAttr("mode", ListMode.AsStringCodec) + lazy val noDataText: ReactiveHtmlAttr[String] = customHtmlAttr("no-data-text", StringAsIsCodec) + lazy val separators: ReactiveHtmlAttr[ListSeparator] = customHtmlAttr("separators", ListSeparator.AsStringCodec) object events { - val onItemClick = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[Li.Ref]]]("item-click") - val onItemClose = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[Li.Ref]]]("item-close") - val onItemDelete = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[Li.Ref]]]("item-delete") - val onItemToggle = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[Li.Ref]]]("item-toggle") + val onItemClick = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[item.Ref]]]("item-click") + val onItemClose = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[item.Ref]]]("item-close") + val onItemDelete = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[item.Ref]]]("item-delete") + val onItemToggle = new EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[item.Ref]]]("item-toggle") val onLoadMore = new EventProp[EventWithPreciseTarget[Ref]]("load-more") @js.native trait SelectionChangeDetail extends js.Object { @JSName("selectedItems") - def selectedItemsJS: js.Array[Li.Ref] = js.native + def selectedItemsJS: js.Array[item.Ref] = js.native @JSName("previouslySelectedItems") - def previouslySelectedItemsJS: js.Array[Li.Ref] = js.native + def previouslySelectedItemsJS: js.Array[item.Ref] = js.native } object SelectionChangeDetail { extension (detail: SelectionChangeDetail) - def selectedItems: List[Li.Ref] = detail.selectedItemsJS.toList - def previouslySelectedItems: List[Li.Ref] = detail.previouslySelectedItemsJS.toList + def selectedItems: List[item.Ref] = detail.selectedItemsJS.toList + def previouslySelectedItems: List[item.Ref] = detail.previouslySelectedItemsJS.toList /** Returns the first selected item when it exists (useful in [[ListMode.SingleSelect]]) */ - def maybeSelectedItem: Option[Li.Ref] = detail.selectedItemsJS.headOption + def maybeSelectedItem: Option[item.Ref] = detail.selectedItemsJS.headOption /** Returns the first previously selected item when it exists (useful in [[ListMode.SingleSelect]])) */ - def maybePreviouslySelectedItem: Option[Li.Ref] = detail.previouslySelectedItemsJS.headOption + def maybePreviouslySelectedItem: Option[item.Ref] = detail.previouslySelectedItemsJS.headOption } val onSelectionChange = @@ -87,7 +87,14 @@ object UList { def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(UList)): _*) - val Li: ListItem.type = ListItem - def group: UListGroupHeader.type = UListGroupHeader + @deprecated("Li was a badly designed name. Use `item` instead", "15/08/2022") + def Li: ListItem.type = ListItem + + def item: ListItem.type = ListItem + def customItem: CustomListItem.type = CustomListItem + def group: UListGroupHeader.type = UListGroupHeader + + def notificationItem: NotificationListItem.type = NotificationListItem + def notificationGroup: NotificationListGroupItem.type = NotificationListGroupItem } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UListGroupHeader.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UListGroupHeader.scala index dca8fcc..89e3f74 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UListGroupHeader.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UListGroupHeader.scala @@ -11,29 +11,32 @@ import scala.scalajs.js import scala.scalajs.js.annotation.JSImport import be.doeraene.webcomponents.ui5.internal.Slot import be.doeraene.webcomponents.ui5.eventtypes.{HasDetail, HasItem, HasTargetRef} +import be.doeraene.webcomponents.WebComponent -/** Simple UI button +/** The ui5-li-groupheader is a special list item, used only to separate other list items into logical groups. * * @see * the doc for more information. */ -object UListGroupHeader { +object UListGroupHeader extends WebComponent { @js.native trait RawElement extends js.Object {} + @js.native + @JSImport("@ui5/webcomponents/dist/List.js", JSImport.Default) + object RawImport extends js.Object + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination used(RawImport) type Ref = dom.html.Element with RawElement type ModFunction = UListGroupHeader.type => Mod[ReactiveHtmlElement[Ref]] - private val tag: HtmlTag[Ref] = customHtmlTag("ui5-groupheader") - - val id: ReactiveProp[String, String] = idAttr + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-li-groupheader") - val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) - val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) object slots {} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UploadCollection.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UploadCollection.scala new file mode 100644 index 0000000..6b0779b --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UploadCollection.scala @@ -0,0 +1,87 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSImport, JSName} +import be.doeraene.webcomponents.ui5.configkeys.ListMode +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.ui5.eventtypes.HasItem +import be.doeraene.webcomponents.WebComponent + +/** This component allows you to represent files before uploading them to a server, with the help of + * ui5-upload-collection-item. It also allows you to show already uploaded files. + * + * @see + * the doc for more + * information. + */ +object UploadCollection extends WebComponent { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/UploadCollection.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = UploadCollection.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-upload-collection") + + lazy val accessibleName: ReactiveHtmlAttr[String] = customHtmlAttr("accessible-name", StringAsIsCodec) + + lazy val hideDragOverlay: ReactiveHtmlAttr[Boolean] = customHtmlAttr("hide-drag-overlay", BooleanAsAttrPresenceCodec) + + lazy val mode: ReactiveHtmlAttr[ListMode] = customHtmlAttr("mode", ListMode.AsStringCodec) + + lazy val noDataDescription: ReactiveHtmlAttr[String] = customHtmlAttr("no-data-description", StringAsIsCodec) + + lazy val noDataText: ReactiveHtmlAttr[String] = customHtmlAttr("no-data-text", StringAsIsCodec) + + object slots { + val header: Slot = new Slot("header") + } + + object events { + + trait HasDataTransfer extends js.Object { + def dataTransfer: dom.DataTransfer + } + + val onDrop: EventProp[EventWithPreciseTarget[Ref] & HasDataTransfer] = new EventProp("drop") + val onItemDelete: EventProp[EventWithPreciseTarget[Ref] & HasDetail[HasItem[dom.HTMLElement]]] = new EventProp( + "item-delete" + ) + + trait SelectionChangeInfo extends js.Object { + @JSName("selectedItems") + def selectedItemsJS: Array[dom.HTMLElement] + } + + object SelectionChangeInfo { + extension (info: SelectionChangeInfo) def selectedItems: List[dom.HTMLElement] = info.selectedItemsJS.toList + } + + val onSelectionChange: EventProp[EventWithPreciseTarget[Ref] & HasDetail[SelectionChangeInfo]] = new EventProp( + "selection-change" + ) + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(UploadCollection)): _*) + + def item: UploadCollectionItem.type = UploadCollectionItem + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UploadCollectionItem.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UploadCollectionItem.scala new file mode 100644 index 0000000..a4dfa95 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/UploadCollectionItem.scala @@ -0,0 +1,88 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.{ButtonDesign, ColourScheme, IconName} +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSImport, JSName} +import be.doeraene.webcomponents.ui5.configkeys.{ListMode, UploadState} +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.ui5.eventtypes.HasItem +import com.raquo.domtypes.generic.codecs.IntAsStringCodec +import be.doeraene.webcomponents.WebComponent + +/** This component allows you to represent files before uploading them to a server, with the help of + * ui5-upload-collection-item. It also allows you to show already uploaded files. + * + * @see + * the doc for more + * information. + */ +object UploadCollectionItem extends WebComponent { + + @js.native + trait RawElement extends js.Object { + def fileJS: dom.File | Null = js.native + + def progress: Int = js.native + + def fileName: String = js.native + } + + object RawElement { + extension (element: RawElement) def file: Option[dom.File] = Option(element.fileJS) + } + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/UploadCollectionItem.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = UploadCollectionItem.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-upload-collection-item") + + lazy val disableDeleteButton: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("disable-delete-button", BooleanAsAttrPresenceCodec) + + lazy val fileName: ReactiveHtmlAttr[String] = + customHtmlAttr("file-name", StringAsIsCodec) + + lazy val fileNameClickable: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("file-name-clickable", BooleanAsAttrPresenceCodec) + + lazy val hideRetryButton: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("hide-retry-button", BooleanAsAttrPresenceCodec) + + lazy val hideTerminateButton: ReactiveHtmlAttr[Boolean] = + customHtmlAttr("hide-terminate-button", BooleanAsAttrPresenceCodec) + + lazy val progress: ReactiveHtmlAttr[Int] = customHtmlAttr("progress", IntAsStringCodec) + + lazy val uploadState: ReactiveHtmlAttr[UploadState] = + customHtmlAttr("upload-state", UploadState.AsStringCodec) + + object slots { + val thumbnail: Slot = new Slot("thumbnail") + } + + object events { + val onFileNameClick: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("file-name-click") + val onRename: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("rename") + val onRetry: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("retry") + val onTerminate: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("terminate") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(UploadCollectionItem)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ViewSettingsDialog.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ViewSettingsDialog.scala new file mode 100644 index 0000000..92badfe --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/ViewSettingsDialog.scala @@ -0,0 +1,105 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.* +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSImport, JSName} +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.WebComponent + +/** The ui5-view-settings-dialog component helps the user to sort data within a list or a table. It consists of several + * lists like Sort order which is built-in and Sort By and Filter By lists, for which you must be provide + * items(ui5-sort-item & ui5-filter-item respectively) These options can be used to create sorters for a table. The + * ui5-view-settings-dialog interrupts the current application processing as it is the only focused UI element and the + * main screen is dimmed/blocked. The ui5-view-settings-dialog is modal, which means that user action is required + * before returning to the parent window is possible. + * + * @see + * the doc for more + * information. + */ +object ViewSettingsDialog extends WebComponent { + + @js.native + trait RawElement extends js.Object { + def setConfirmedSettings(settings: ViewSettings): Unit = js.native + + def show(): Unit = js.native + } + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/ViewSettingsDialog.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = ViewSettingsDialog.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-view-settings-dialog") + + lazy val sortDescending: ReactiveHtmlAttr[Boolean] = customHtmlAttr("sort-descending", BooleanAsAttrPresenceCodec) + + object slots { + val filterItems: Slot = Slot("filterItems") + val sortItems: Slot = Slot("sortItems") + } + + trait ViewSettings extends js.Object { + def sortOrder: "Ascending" | "Descending" + + def sortBy: String + + def sortDescending: Boolean + + @JSName("filters") + def filtersJS: js.Array[js.Dictionary[js.Array[String]]] + } + + object ViewSettings { + extension (settings: ViewSettings) + def filters: Map[String, List[String]] = + settings.filtersJS.flatMap(_.toMap.map((key, values) => (key, values.toList))).toMap + } + + object events { + val onBeforeOpen: EventProp[EventWithPreciseTarget[Ref]] = new EventProp("before-open") + + trait HasSortByItem extends js.Object { + def sortByItem: dom.HTMLElement + } + + val onCancel: EventProp[EventWithPreciseTarget[Ref] & HasDetail[ViewSettings & HasSortByItem]] = new EventProp( + "cancel" + ) + val onConfirm: EventProp[EventWithPreciseTarget[Ref] & HasDetail[ViewSettings & HasSortByItem]] = new EventProp( + "confirm" + ) + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(ViewSettingsDialog)): _*) + + /** Feed an instance of [[ViewSettingsDialog]] ref to this observer in order to show it. */ + val showObserver: Observer[Ref] = Observer(_.show()) + + /** [[Mod]] showing the [[ViewSettingsDialog]] when the specified stream emits. */ + def showFromEvents(viewSettingsDialogShowEvents: EventStream[Unit]) = + inContext[ReactiveHtmlElement[Ref]](el => viewSettingsDialogShowEvents.mapTo(el.ref) --> showObserver) + + /** Feed an instance of [[ViewSettingsDialog]] ref with the desired [[ViewSettings]] to set these to it. */ + val setConfirmedSettingsObserver: Observer[(Ref, ViewSettings)] = Observer(_.setConfirmedSettings(_)) + + /** [[Mod]] settings the [[ViewSettings]] to the [[ViewSettingsDialog]]. */ + def setConfirmedSettingsFromEvents(settingsEvent: EventStream[ViewSettings]) = + inContext[ReactiveHtmlElement[Ref]](el => settingsEvent.map(el.ref -> _) --> setConfirmedSettingsObserver) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Wizard.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Wizard.scala new file mode 100644 index 0000000..dd47d49 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/Wizard.scala @@ -0,0 +1,57 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.* +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.ui5.eventtypes.EventWithPreciseTarget +import be.doeraene.webcomponents.ui5.eventtypes.HasDetail +import be.doeraene.webcomponents.WebComponent + +/** The ui5-wizard helps users to complete a complex task by dividing it into sections and guiding them through it. It + * has two main areas - a navigation area at the top showing the step sequence and a content area below it. + * + * @see + * the doc for more information. + */ +object Wizard extends WebComponent { + + @js.native + trait RawElement extends js.Object {} + + @js.native + @JSImport("@ui5/webcomponents-fiori/dist/Wizard.js", JSImport.Default) + object RawImport extends js.Object + + // object-s are lazy so you need to actually use them in your code to prevent dead code elimination + used(RawImport) + + type Ref = dom.html.Element with RawElement + type ModFunction = Wizard.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-wizard") + + object slots {} + + object events { + trait StepChangeInfo extends js.Object { + def step: WizardStep.Ref + def previousStep: WizardStep.Ref + def changeWithClick: Boolean + } + + val onStepChange: EventProp[EventWithPreciseTarget[Ref] & HasDetail[StepChangeInfo]] = new EventProp("step-change") + } + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(Wizard)): _*) + + def step: WizardStep.type = WizardStep + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/WizardStep.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/WizardStep.scala new file mode 100644 index 0000000..9f3070e --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/WizardStep.scala @@ -0,0 +1,48 @@ +package be.doeraene.webcomponents.ui5 + +import be.doeraene.webcomponents.ui5.configkeys.* +import be.doeraene.webcomponents.ui5.internal.Slot +import com.raquo.domtypes.generic.codecs.{BooleanAsAttrPresenceCodec, StringAsIsCodec} +import com.raquo.laminar.api.L.* +import com.raquo.laminar.builders.HtmlTag +import com.raquo.laminar.keys.{ReactiveHtmlAttr, ReactiveProp, ReactiveStyle} +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import be.doeraene.webcomponents.WebComponent + +/** A component that represents a logical step as part of the ui5-wizard. It is meant to aggregate arbitrary HTML + * elements that form the content of a single step. + * + * @see + * the doc for more information. + */ +object WizardStep extends WebComponent with HasIcon { + + @js.native + trait RawElement extends js.Object {} + + type Ref = dom.html.Element with RawElement + type ModFunction = WizardStep.type => Mod[ReactiveHtmlElement[Ref]] + + private val tag: HtmlTag[Ref] = customHtmlTag("ui5-wizard-step") + + lazy val branching: ReactiveHtmlAttr[Boolean] = customHtmlAttr("branching", BooleanAsAttrPresenceCodec) + + lazy val disabled: ReactiveHtmlAttr[Boolean] = customHtmlAttr("disabled", BooleanAsAttrPresenceCodec) + + lazy val selected: ReactiveHtmlAttr[Boolean] = customHtmlAttr("selected", BooleanAsAttrPresenceCodec) + + lazy val subtitleText: ReactiveHtmlAttr[String] = customHtmlAttr("subtitle-text", StringAsIsCodec) + + lazy val titleText: ReactiveHtmlAttr[String] = customHtmlAttr("title-text", StringAsIsCodec) + + object slots {} + + object events {} + + def apply(mods: ModFunction*): HtmlElement = tag(mods.map(_(WizardStep)): _*) + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/AvatarGroupType.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/AvatarGroupType.scala new file mode 100644 index 0000000..32f140e --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/AvatarGroupType.scala @@ -0,0 +1,15 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait AvatarGroupType { + def value: String = toString +} + +object AvatarGroupType extends EnumerationString[AvatarGroupType] { + case object Group extends AvatarGroupType + case object Individual extends AvatarGroupType + + val allValues: List[AvatarGroupType] = List(Group, Individual) + + def valueOf(value: AvatarGroupType): String = value.value + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/ButtonDesign.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/ButtonDesign.scala index 02d1d36..6c52d2e 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/ButtonDesign.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/ButtonDesign.scala @@ -2,7 +2,9 @@ package be.doeraene.webcomponents.ui5.configkeys import com.raquo.domtypes.generic.codecs.Codec -sealed trait ButtonDesign +sealed trait ButtonDesign { + def value: String = toString +} object ButtonDesign extends EnumerationString[ButtonDesign] { @@ -22,6 +24,6 @@ object ButtonDesign extends EnumerationString[ButtonDesign] { Attention ) - def valueOf(value: ButtonDesign): String = value.toString + def valueOf(value: ButtonDesign): String = value.value } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/CalendarSelectionMode.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/CalendarSelectionMode.scala new file mode 100644 index 0000000..45cb28a --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/CalendarSelectionMode.scala @@ -0,0 +1,15 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait CalendarSelectionMode { + def value: String = toString +} + +object CalendarSelectionMode extends EnumerationString[CalendarSelectionMode] { + case object Single extends CalendarSelectionMode + case object Range extends CalendarSelectionMode + case object Multiple extends CalendarSelectionMode + + val allValues: List[CalendarSelectionMode] = List(Single, Range, Multiple) + + def valueOf(value: CalendarSelectionMode): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/CalendarType.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/CalendarType.scala index 0293676..c7cc39f 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/CalendarType.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/CalendarType.scala @@ -5,33 +5,52 @@ import scala.scalajs.js.annotation.JSImport trait CalendarType { def value: String = toString + + /** You can call this object wherever in your code to be sure that you have support for this [[CalendarType]] */ + def importObject: CalendarType.CalendarTypeImporter[this.type] } object CalendarType extends EnumerationString[CalendarType] { // /!\ To use any but the Gregorian type, you need to call one of the imports below anywhere in your code. - case object Gregorian extends CalendarType - case object Buddhist extends CalendarType - case object Islamic extends CalendarType - case object Japanese extends CalendarType - case object Persian extends CalendarType + case object Gregorian extends CalendarType { + def importObject: CalendarType.CalendarTypeImporter[this.type] = GregorianImport + } + case object Buddhist extends CalendarType { + def importObject: CalendarType.CalendarTypeImporter[this.type] = BuddhistImport + } + case object Islamic extends CalendarType { + def importObject: CalendarType.CalendarTypeImporter[this.type] = IslamicImport + } + case object Japanese extends CalendarType { + def importObject: CalendarType.CalendarTypeImporter[this.type] = JapaneseImport + } + case object Persian extends CalendarType { + def importObject: CalendarType.CalendarTypeImporter[this.type] = PersianImport + } + + /** Marker trait to specify the the object is used to import a specific calendar. */ + sealed trait CalendarTypeImporter[For <: CalendarType] extends js.Object + + // Gregorian calendar is imported by default, so this object is here only for consistency + object GregorianImport extends CalendarTypeImporter[Gregorian.type] @js.native @JSImport("@ui5/webcomponents-localization/dist/features/calendar/Buddhist.js", JSImport.Default) - object BuddhistImport extends js.Object + object BuddhistImport extends CalendarTypeImporter[Buddhist.type] @js.native @JSImport("@ui5/webcomponents-localization/dist/features/calendar/Islamic.js", JSImport.Default) - object IslamicImport extends js.Object + object IslamicImport extends CalendarTypeImporter[Islamic.type] @js.native @JSImport("@ui5/webcomponents-localization/dist/features/calendar/Japanese.js", JSImport.Default) - object JapaneseImport extends js.Object + object JapaneseImport extends CalendarTypeImporter[Japanese.type] @js.native @JSImport("@ui5/webcomponents-localization/dist/features/calendar/Persian.js", JSImport.Default) - object PersianImport extends js.Object + object PersianImport extends CalendarTypeImporter[Persian.type] val allValues: List[CalendarType] = List(Gregorian, Buddhist, Islamic, Japanese, Persian) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryItemLayout.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryItemLayout.scala new file mode 100644 index 0000000..3ba68dc --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryItemLayout.scala @@ -0,0 +1,14 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait MediaGalleryItemLayout { + def value: String = toString +} + +object MediaGalleryItemLayout extends EnumerationString[MediaGalleryItemLayout] { + case object Square extends MediaGalleryItemLayout + case object Wide extends MediaGalleryItemLayout + + val allValues: List[MediaGalleryItemLayout] = List(Square, Wide) + + def valueOf(value: MediaGalleryItemLayout): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryLayout.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryLayout.scala new file mode 100644 index 0000000..2da972b --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryLayout.scala @@ -0,0 +1,16 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait MediaGalleryLayout { + def value: String = toString +} + +object MediaGalleryLayout extends EnumerationString[MediaGalleryLayout] { + case object Auto extends MediaGalleryLayout + case object Vertical extends MediaGalleryLayout + case object Horizontal extends MediaGalleryLayout + + val allValues: List[MediaGalleryLayout] = List(Auto, Vertical, Horizontal) + + def valueOf(value: MediaGalleryLayout): String = value.value + +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryMenuHorizontalAlign.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryMenuHorizontalAlign.scala new file mode 100644 index 0000000..4b74ce6 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryMenuHorizontalAlign.scala @@ -0,0 +1,14 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait MediaGalleryMenuHorizontalAlign { + def value: String = toString +} + +object MediaGalleryMenuHorizontalAlign extends EnumerationString[MediaGalleryMenuHorizontalAlign] { + case object Left extends MediaGalleryMenuHorizontalAlign + case object Right extends MediaGalleryMenuHorizontalAlign + + val allValues: List[MediaGalleryMenuHorizontalAlign] = List(Left, Right) + + def valueOf(value: MediaGalleryMenuHorizontalAlign): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryMenuVerticalAlign.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryMenuVerticalAlign.scala new file mode 100644 index 0000000..5351b70 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/MediaGalleryMenuVerticalAlign.scala @@ -0,0 +1,14 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait MediaGalleryMenuVerticalAlign { + def value: String = toString +} + +object MediaGalleryMenuVerticalAlign extends EnumerationString[MediaGalleryMenuVerticalAlign] { + case object Top extends MediaGalleryMenuVerticalAlign + case object Bottom extends MediaGalleryMenuVerticalAlign + + val allValues: List[MediaGalleryMenuVerticalAlign] = List(Top, Bottom) + + def valueOf(value: MediaGalleryMenuVerticalAlign): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/PageBackgroundDesign.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/PageBackgroundDesign.scala new file mode 100644 index 0000000..9c00838 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/PageBackgroundDesign.scala @@ -0,0 +1,17 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait PageBackgroundDesign { + def value: String = toString +} + +object PageBackgroundDesign extends EnumerationString[PageBackgroundDesign] { + case object Solid extends PageBackgroundDesign + case object Transparent extends PageBackgroundDesign + case object ListPage extends PageBackgroundDesign { + override def value: String = "List" + } + + val allValues: List[PageBackgroundDesign] = List(Solid, Transparent, ListPage) + + def valueOf(value: PageBackgroundDesign): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/PopoverHorizontalAlign.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/PopoverHorizontalAlign.scala new file mode 100644 index 0000000..d6745d5 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/PopoverHorizontalAlign.scala @@ -0,0 +1,17 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait PopoverHorizontalAlign { + def value: String = toString + +} + +object PopoverHorizontalAlign extends EnumerationString[PopoverHorizontalAlign] { + case object Center extends PopoverHorizontalAlign + case object Left extends PopoverHorizontalAlign + case object Right extends PopoverHorizontalAlign + case object Stretch extends PopoverHorizontalAlign + + val allValues: List[PopoverHorizontalAlign] = List(Center, Left, Right, Stretch) + + def valueOf(value: PopoverHorizontalAlign): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/PopoverVerticalAlign.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/PopoverVerticalAlign.scala new file mode 100644 index 0000000..e7286cc --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/PopoverVerticalAlign.scala @@ -0,0 +1,17 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait PopoverVerticalAlign { + def value: String = toString + +} + +object PopoverVerticalAlign extends EnumerationString[PopoverVerticalAlign] { + case object Center extends PopoverVerticalAlign + case object Top extends PopoverVerticalAlign + case object Bottom extends PopoverVerticalAlign + case object Stretch extends PopoverVerticalAlign + + val allValues: List[PopoverVerticalAlign] = List(Center, Top, Bottom, Stretch) + + def valueOf(value: PopoverVerticalAlign): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/Priority.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/Priority.scala new file mode 100644 index 0000000..b639560 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/Priority.scala @@ -0,0 +1,16 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait Priority { + def value: String = toString +} + +object Priority extends EnumerationString[Priority] { + case object None extends Priority + case object Low extends Priority + case object Medium extends Priority + case object High extends Priority + + val allValues: List[Priority] = List(None, Low, Medium, High) + + def valueOf(value: Priority): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/SideContentFallDown.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/SideContentFallDown.scala new file mode 100644 index 0000000..8fc5f20 --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/SideContentFallDown.scala @@ -0,0 +1,16 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait SideContentFallDown { + def value: String = toString +} + +object SideContentFallDown extends EnumerationString[SideContentFallDown] { + case object BelowXL extends SideContentFallDown + case object BelowL extends SideContentFallDown + case object BelowM extends SideContentFallDown + case object OnMinimumWidth extends SideContentFallDown + + val allValues: List[SideContentFallDown] = List(BelowXL, BelowL, BelowM, OnMinimumWidth) + + def valueOf(value: SideContentFallDown): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/SideContentPosition.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/SideContentPosition.scala new file mode 100644 index 0000000..9ba59ca --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/SideContentPosition.scala @@ -0,0 +1,14 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait SideContentPosition { + def value: String = toString +} + +object SideContentPosition extends EnumerationString[SideContentPosition] { + case object Start extends SideContentPosition + case object End extends SideContentPosition + + val allValues: List[SideContentPosition] = List(Start, End) + + def valueOf(value: SideContentPosition): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/SideContentVisibility.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/SideContentVisibility.scala new file mode 100644 index 0000000..b1d339f --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/SideContentVisibility.scala @@ -0,0 +1,17 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait SideContentVisibility { + def value: String = toString +} + +object SideContentVisibility extends EnumerationString[SideContentVisibility] { + case object AlwaysShow extends SideContentVisibility + case object ShowAboveL extends SideContentVisibility + case object ShowAboveM extends SideContentVisibility + case object ShowAboveS extends SideContentVisibility + case object NeverShow extends SideContentVisibility + + val allValues: List[SideContentVisibility] = List(AlwaysShow, ShowAboveL, ShowAboveM, ShowAboveS, NeverShow) + + def valueOf(value: SideContentVisibility): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/UploadState.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/UploadState.scala new file mode 100644 index 0000000..d5262cd --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/configkeys/UploadState.scala @@ -0,0 +1,15 @@ +package be.doeraene.webcomponents.ui5.configkeys + +sealed trait UploadState { + def value: String = toString +} + +object UploadState extends EnumerationString[UploadState] { + case object Ready extends UploadState + case object Uploading extends UploadState + case object Error extends UploadState + + val allValues: List[UploadState] = List(Ready, Uploading, Error) + + def valueOf(value: UploadState): String = value.value +} diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/package.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/package.scala index 97066c1..7ff46be 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/package.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/package.scala @@ -34,4 +34,14 @@ package object ui5 { override def encode(scalaValue: LocalDate): String = scalaValue.format(formatter) } + case class ListCodec[A](codec: Codec[A, String]) extends Codec[List[A], String] { + def decode(domValue: String): List[A] = domValue.split(',').toList.map(codec.decode) + + def encode(scalaValue: List[A]): String = scalaValue.map(codec.encode).mkString(",") + } + + @js.native + @JSImport("@ui5/webcomponents/dist/features/InputElementsFormSupport.js", JSImport.Default) + object SubmitsSupport extends js.Object + } diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/scaladsl/colour/Colour.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/scaladsl/colour/Colour.scala index 5684bcc..441122d 100644 --- a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/scaladsl/colour/Colour.scala +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/scaladsl/colour/Colour.scala @@ -65,21 +65,7 @@ object Colour { * * Behaviour for invalid strings is undefined. */ - def fromString(str: String): Colour = { - val canvas = dom.document.createElement("canvas").asInstanceOf[dom.HTMLCanvasElement] - canvas.width = 1 - canvas.height = 1 - val ctx = canvas.getContext("2d").asInstanceOf[dom.CanvasRenderingContext2D] - ctx.fillStyle = str - ctx.fillRect(0, 0, 1, 1) - val data = ctx.getImageData(0, 0, 1, 1).data - val red = data(0) - val green = data(1) - val blue = data(2) - val alpha = data(3) / 255.0 - - apply(red, green, blue, alpha) - } + def fromString(str: String)(using cache: FromStringColourCache): Colour = cache.fromString(str) // some predefined colours val black: Colour = fromIntColour(0) diff --git a/web-components/src/main/scala/be/doeraene/webcomponents/ui5/scaladsl/colour/FromStringColourCache.scala b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/scaladsl/colour/FromStringColourCache.scala new file mode 100644 index 0000000..eff074b --- /dev/null +++ b/web-components/src/main/scala/be/doeraene/webcomponents/ui5/scaladsl/colour/FromStringColourCache.scala @@ -0,0 +1,67 @@ +package be.doeraene.webcomponents.ui5.scaladsl.colour + +import org.scalajs.dom + +import scala.collection.mutable + +/** The purpose of a [[FromStringColourCache]] is to circumvent the fact that actually pulling a [[Colour]] from a + * [[String]] is a quite expensive operation. Indeed, one has to create an actual canvas, paint to it, and retrieve the + * colour data back from it. + * + * In order to seemlessly use the fromString method in the companion object of [[Colour]], there is a default cache + * implementation (given -- pun intended -- below) that simply caches the last 100 different seen values. + * + * If you want, you can provide a custom implementation and use that one. + * + * Note that the mutability aspect, as implemented here, is not a liability since we run this thing on JS, where only + * one thread exists. + */ +trait FromStringColourCache { + def fromString(colourString: String): Colour +} + +object FromStringColourCache { + + val noCache: FromStringColourCache = new FromStringColourCache { + val canvas = dom.document.createElement("canvas").asInstanceOf[dom.HTMLCanvasElement] + canvas.width = 1 + canvas.height = 1 + val ctx = canvas.getContext("2d").asInstanceOf[dom.CanvasRenderingContext2D] + + def fromString(colourString: String): Colour = { + ctx.fillStyle = colourString + ctx.fillRect(0, 0, 1, 1) + val data = ctx.getImageData(0, 0, 1, 1).data + val red = data(0) + val green = data(1) + val blue = data(2) + val alpha = data(3) / 255.0 + + Colour(red, green, blue, alpha) + } + } + + def lastNCache(cacheSize: Int): FromStringColourCache = new FromStringColourCache { + var oldestCacheValue: String = "white" + var cachedValues: mutable.Map[String, Colour] = mutable.Map("white" -> Colour.white) + var numberOfCachedValues: Int = 0 + + def fromString(colourString: String): Colour = + cachedValues.get(colourString) match { + case Some(colour) => colour + case None => + val colour = noCache.fromString(colourString) + if numberOfCachedValues == cacheSize then + cachedValues -= oldestCacheValue + oldestCacheValue = colourString + else numberOfCachedValues += 1 + + cachedValues += (colourString -> colour) + + colour + } + } + + given FromStringColourCache = lastNCache(100) + +}