diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala deleted file mode 100644 index 10a322c..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala +++ /dev/null @@ -1,87 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.model.FileRef - -trait DetailComponentsModule: - self: IconsModule => - object details: - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]], - actions: Modifier[HtmlElement]* - )(fields: HtmlElement*): HtmlElement = - div( - div( - cls := "px-4 sm:px-0", - h3( - cls := "text-base font-semibold leading-7 text-gray-900", - title - ), - subtitle.map(st => - p( - cls := "mt-1 max-w-2xl text-sm leading-6 text-gray-500", - st - ) - ) - // TODO: actions - ), - div( - cls := "mt-6 border-t border-gray-100", - dl( - cls := "divide-y divide-gray-100", - fields - ) - ) - ) - - def field( - title: String, - content: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls := "px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0", - dt( - cls := "text-sm font-medium leading-6 text-gray-900", - title - ), - dd( - cls := "mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0", - content - ) - ) - - def files(fs: List[FileRef]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-100 rounded-md border border-gray-200", - fs.map(file) - ) - - def file(f: FileRef): HtmlElement = - li( - cls := "flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6", - div( - cls := "flex w-0 flex-1 items-center", - icons.`paper-clip-solid`(), - div( - cls := "ml-4 flex min-w-0 flex-1 gap-2", - span( - cls := "truncate font-medium", - f.name - ), - f.size.map(size => span(cls := "flex-shrink-0 text-gray-400", size)) - ) - ), - div( - cls := "ml-4 flex-shrink-0", - a( - href := "#", - cls := "font-medium text-indigo-600 hover:text-indigo-500", - "Uložit" - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala deleted file mode 100644 index 10a322c..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala +++ /dev/null @@ -1,87 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.model.FileRef - -trait DetailComponentsModule: - self: IconsModule => - object details: - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]], - actions: Modifier[HtmlElement]* - )(fields: HtmlElement*): HtmlElement = - div( - div( - cls := "px-4 sm:px-0", - h3( - cls := "text-base font-semibold leading-7 text-gray-900", - title - ), - subtitle.map(st => - p( - cls := "mt-1 max-w-2xl text-sm leading-6 text-gray-500", - st - ) - ) - // TODO: actions - ), - div( - cls := "mt-6 border-t border-gray-100", - dl( - cls := "divide-y divide-gray-100", - fields - ) - ) - ) - - def field( - title: String, - content: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls := "px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0", - dt( - cls := "text-sm font-medium leading-6 text-gray-900", - title - ), - dd( - cls := "mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0", - content - ) - ) - - def files(fs: List[FileRef]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-100 rounded-md border border-gray-200", - fs.map(file) - ) - - def file(f: FileRef): HtmlElement = - li( - cls := "flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6", - div( - cls := "flex w-0 flex-1 items-center", - icons.`paper-clip-solid`(), - div( - cls := "ml-4 flex min-w-0 flex-1 gap-2", - span( - cls := "truncate font-medium", - f.name - ), - f.size.map(size => span(cls := "flex-shrink-0 text-gray-400", size)) - ) - ), - div( - cls := "ml-4 flex-shrink-0", - a( - href := "#", - cls := "font-medium text-indigo-600 hover:text-indigo-500", - "Uložit" - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala deleted file mode 100644 index eb6dd5f..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala +++ /dev/null @@ -1,101 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import org.scalajs.dom.html -import com.raquo.laminar.modifiers.KeyUpdater - -trait FormComponentsModule: - self: IconsModule => - - object forms: - - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls("space-y-6 sm:space-y-5"), - div( - h3(cls("text-lg leading-6 font-medium text-gray-900"), title), - subtitle.map(st => p(cls("mt-1 max-w-2xl text-sm text-gray-500"), st)) - ), - div(cls("mt-6 sm:mt-5 space-y-6 sm:space-y-5"), content) - ) - - def label(labelText: String, forId: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.label( - cls("block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"), - forId.map(id => L.forId(id)), - labelText, - mods - ) - - def field( - label: Modifier[HtmlElement] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls( - "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5" - ), - label, - div(cls("mt-1 sm:mt-0 sm:col-span-2"), content) - ) - - def field( - id: String, - labelText: String, - input: HtmlElement, - help: Option[String] - ): HtmlElement = - field( - label(labelText, Some(id))() - )( - input.amend(idAttr(id)), - help.map(h => p(cls("mt-2 text-sm text-gray-500"), h)) - ) - - def form(mods: Modifier[HtmlElement]*)( - sections: Modifier[HtmlElement]* - )(actions: Modifier[HtmlElement]*): HtmlElement = - L.form( - cls("space-y-8 divide-y divide-gray-200"), - mods, - sections, - div( - cls("pt-5"), - div(cls("flex justify-end"), actions) - ) - ) - - def inlineForm( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.form(cls("flex space-x-4"), mods) - - def inputField( - id: String, - labelText: String, - placeholderText: Option[String] = None, - inputType: String = "text", - helpText: Option[String] = None - ): HtmlElement = - field( - id, - labelText, - input( - tpe(inputType), - cls( - "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6" - ), - placeholderText.map(placeholder(_)) - ), - helpText - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala deleted file mode 100644 index 10a322c..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala +++ /dev/null @@ -1,87 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.model.FileRef - -trait DetailComponentsModule: - self: IconsModule => - object details: - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]], - actions: Modifier[HtmlElement]* - )(fields: HtmlElement*): HtmlElement = - div( - div( - cls := "px-4 sm:px-0", - h3( - cls := "text-base font-semibold leading-7 text-gray-900", - title - ), - subtitle.map(st => - p( - cls := "mt-1 max-w-2xl text-sm leading-6 text-gray-500", - st - ) - ) - // TODO: actions - ), - div( - cls := "mt-6 border-t border-gray-100", - dl( - cls := "divide-y divide-gray-100", - fields - ) - ) - ) - - def field( - title: String, - content: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls := "px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0", - dt( - cls := "text-sm font-medium leading-6 text-gray-900", - title - ), - dd( - cls := "mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0", - content - ) - ) - - def files(fs: List[FileRef]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-100 rounded-md border border-gray-200", - fs.map(file) - ) - - def file(f: FileRef): HtmlElement = - li( - cls := "flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6", - div( - cls := "flex w-0 flex-1 items-center", - icons.`paper-clip-solid`(), - div( - cls := "ml-4 flex min-w-0 flex-1 gap-2", - span( - cls := "truncate font-medium", - f.name - ), - f.size.map(size => span(cls := "flex-shrink-0 text-gray-400", size)) - ) - ), - div( - cls := "ml-4 flex-shrink-0", - a( - href := "#", - cls := "font-medium text-indigo-600 hover:text-indigo-500", - "Uložit" - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala deleted file mode 100644 index eb6dd5f..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala +++ /dev/null @@ -1,101 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import org.scalajs.dom.html -import com.raquo.laminar.modifiers.KeyUpdater - -trait FormComponentsModule: - self: IconsModule => - - object forms: - - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls("space-y-6 sm:space-y-5"), - div( - h3(cls("text-lg leading-6 font-medium text-gray-900"), title), - subtitle.map(st => p(cls("mt-1 max-w-2xl text-sm text-gray-500"), st)) - ), - div(cls("mt-6 sm:mt-5 space-y-6 sm:space-y-5"), content) - ) - - def label(labelText: String, forId: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.label( - cls("block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"), - forId.map(id => L.forId(id)), - labelText, - mods - ) - - def field( - label: Modifier[HtmlElement] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls( - "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5" - ), - label, - div(cls("mt-1 sm:mt-0 sm:col-span-2"), content) - ) - - def field( - id: String, - labelText: String, - input: HtmlElement, - help: Option[String] - ): HtmlElement = - field( - label(labelText, Some(id))() - )( - input.amend(idAttr(id)), - help.map(h => p(cls("mt-2 text-sm text-gray-500"), h)) - ) - - def form(mods: Modifier[HtmlElement]*)( - sections: Modifier[HtmlElement]* - )(actions: Modifier[HtmlElement]*): HtmlElement = - L.form( - cls("space-y-8 divide-y divide-gray-200"), - mods, - sections, - div( - cls("pt-5"), - div(cls("flex justify-end"), actions) - ) - ) - - def inlineForm( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.form(cls("flex space-x-4"), mods) - - def inputField( - id: String, - labelText: String, - placeholderText: Option[String] = None, - inputType: String = "text", - helpText: Option[String] = None - ): HtmlElement = - field( - id, - labelText, - input( - tpe(inputType), - cls( - "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6" - ), - placeholderText.map(placeholder(_)) - ), - helpText - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala deleted file mode 100644 index 52da07e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait HeadingsComponentsModule: - object headings: - def section(title: Node, actions: HtmlElement*): HtmlElement = - div( - cls("border-b border-gray-200 pb-5"), - div( - cls("sm:flex sm:items-center sm:justify-between"), - h3(cls("text-base font-semibold leading-6 text-gray-900"), title), - div( - cls("mt-3 flex sm:ml-4 sm:mt-0"), - actions match - case Nil => emptyMod - case first :: rest => - first :: rest.map(_.amend(cls("ml-3"))) - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala deleted file mode 100644 index 10a322c..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala +++ /dev/null @@ -1,87 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.model.FileRef - -trait DetailComponentsModule: - self: IconsModule => - object details: - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]], - actions: Modifier[HtmlElement]* - )(fields: HtmlElement*): HtmlElement = - div( - div( - cls := "px-4 sm:px-0", - h3( - cls := "text-base font-semibold leading-7 text-gray-900", - title - ), - subtitle.map(st => - p( - cls := "mt-1 max-w-2xl text-sm leading-6 text-gray-500", - st - ) - ) - // TODO: actions - ), - div( - cls := "mt-6 border-t border-gray-100", - dl( - cls := "divide-y divide-gray-100", - fields - ) - ) - ) - - def field( - title: String, - content: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls := "px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0", - dt( - cls := "text-sm font-medium leading-6 text-gray-900", - title - ), - dd( - cls := "mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0", - content - ) - ) - - def files(fs: List[FileRef]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-100 rounded-md border border-gray-200", - fs.map(file) - ) - - def file(f: FileRef): HtmlElement = - li( - cls := "flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6", - div( - cls := "flex w-0 flex-1 items-center", - icons.`paper-clip-solid`(), - div( - cls := "ml-4 flex min-w-0 flex-1 gap-2", - span( - cls := "truncate font-medium", - f.name - ), - f.size.map(size => span(cls := "flex-shrink-0 text-gray-400", size)) - ) - ), - div( - cls := "ml-4 flex-shrink-0", - a( - href := "#", - cls := "font-medium text-indigo-600 hover:text-indigo-500", - "Uložit" - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala deleted file mode 100644 index eb6dd5f..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala +++ /dev/null @@ -1,101 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import org.scalajs.dom.html -import com.raquo.laminar.modifiers.KeyUpdater - -trait FormComponentsModule: - self: IconsModule => - - object forms: - - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls("space-y-6 sm:space-y-5"), - div( - h3(cls("text-lg leading-6 font-medium text-gray-900"), title), - subtitle.map(st => p(cls("mt-1 max-w-2xl text-sm text-gray-500"), st)) - ), - div(cls("mt-6 sm:mt-5 space-y-6 sm:space-y-5"), content) - ) - - def label(labelText: String, forId: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.label( - cls("block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"), - forId.map(id => L.forId(id)), - labelText, - mods - ) - - def field( - label: Modifier[HtmlElement] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls( - "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5" - ), - label, - div(cls("mt-1 sm:mt-0 sm:col-span-2"), content) - ) - - def field( - id: String, - labelText: String, - input: HtmlElement, - help: Option[String] - ): HtmlElement = - field( - label(labelText, Some(id))() - )( - input.amend(idAttr(id)), - help.map(h => p(cls("mt-2 text-sm text-gray-500"), h)) - ) - - def form(mods: Modifier[HtmlElement]*)( - sections: Modifier[HtmlElement]* - )(actions: Modifier[HtmlElement]*): HtmlElement = - L.form( - cls("space-y-8 divide-y divide-gray-200"), - mods, - sections, - div( - cls("pt-5"), - div(cls("flex justify-end"), actions) - ) - ) - - def inlineForm( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.form(cls("flex space-x-4"), mods) - - def inputField( - id: String, - labelText: String, - placeholderText: Option[String] = None, - inputType: String = "text", - helpText: Option[String] = None - ): HtmlElement = - field( - id, - labelText, - input( - tpe(inputType), - cls( - "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6" - ), - placeholderText.map(placeholder(_)) - ), - helpText - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala deleted file mode 100644 index 52da07e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait HeadingsComponentsModule: - object headings: - def section(title: Node, actions: HtmlElement*): HtmlElement = - div( - cls("border-b border-gray-200 pb-5"), - div( - cls("sm:flex sm:items-center sm:justify-between"), - h3(cls("text-base font-semibold leading-6 text-gray-900"), title), - div( - cls("mt-3 flex sm:ml-4 sm:mt-0"), - actions match - case Nil => emptyMod - case first :: rest => - first :: rest.map(_.amend(cls("ml-3"))) - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala deleted file mode 100644 index c543150..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala +++ /dev/null @@ -1,112 +0,0 @@ -package works.iterative.ui.components.laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.CustomAttrs - -trait IconsModule: - object icons: - import svg.* - - object aria: - val hidden = CustomAttrs.svg.ariaHidden - - private def withDefault( - mods: Seq[Modifier[SvgElement]], - default: Modifier[SvgElement] - ): Modifier[SvgElement] = - if mods.isEmpty then default else mods - - def avatarPlaceholder(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-8 w-8"), - fill("none"), - stroke("currentColor"), - viewBox("0 0 24 24"), - xmlns("http://www.w3.org/2000/svg"), - aria.hidden(true), - path( - strokeLineCap("round"), - strokeLineJoin("round"), - strokeWidth("2"), - d( - "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - ) - - def close(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-2 w-2"), - stroke := "currentColor", - fill := "none", - viewBox := "0 0 8 8", - path( - strokeLineCap := "round", - strokeWidth := "1.5", - d := "M1 1l6 6m0-6L1 7" - ) - ) - - def `search-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" - ), - clipRule("evenodd") - ) - ) - - def `filter-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" - ), - clipRule("evenodd") - ) - ) - - def `document-chart-bar-outline`( - mods: Modifier[SvgElement]* - ): SvgElement = - svg( - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - strokeWidth := "1.5", - stroke := "currentColor", - withDefault(mods, cls := "h-6 w-6"), - path( - strokeLineCap := "round", - strokeLineJoin := "round", - d := "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25M9 16.5v.75m3-3v3M15 12v5.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" - ) - ) - - def `paper-clip-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-5 w-5"), - cls := "flex-shrink-0 text-gray-400", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z", - clipRule := "evenodd" - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala deleted file mode 100644 index 10a322c..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala +++ /dev/null @@ -1,87 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.model.FileRef - -trait DetailComponentsModule: - self: IconsModule => - object details: - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]], - actions: Modifier[HtmlElement]* - )(fields: HtmlElement*): HtmlElement = - div( - div( - cls := "px-4 sm:px-0", - h3( - cls := "text-base font-semibold leading-7 text-gray-900", - title - ), - subtitle.map(st => - p( - cls := "mt-1 max-w-2xl text-sm leading-6 text-gray-500", - st - ) - ) - // TODO: actions - ), - div( - cls := "mt-6 border-t border-gray-100", - dl( - cls := "divide-y divide-gray-100", - fields - ) - ) - ) - - def field( - title: String, - content: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls := "px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0", - dt( - cls := "text-sm font-medium leading-6 text-gray-900", - title - ), - dd( - cls := "mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0", - content - ) - ) - - def files(fs: List[FileRef]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-100 rounded-md border border-gray-200", - fs.map(file) - ) - - def file(f: FileRef): HtmlElement = - li( - cls := "flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6", - div( - cls := "flex w-0 flex-1 items-center", - icons.`paper-clip-solid`(), - div( - cls := "ml-4 flex min-w-0 flex-1 gap-2", - span( - cls := "truncate font-medium", - f.name - ), - f.size.map(size => span(cls := "flex-shrink-0 text-gray-400", size)) - ) - ), - div( - cls := "ml-4 flex-shrink-0", - a( - href := "#", - cls := "font-medium text-indigo-600 hover:text-indigo-500", - "Uložit" - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala deleted file mode 100644 index eb6dd5f..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala +++ /dev/null @@ -1,101 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import org.scalajs.dom.html -import com.raquo.laminar.modifiers.KeyUpdater - -trait FormComponentsModule: - self: IconsModule => - - object forms: - - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls("space-y-6 sm:space-y-5"), - div( - h3(cls("text-lg leading-6 font-medium text-gray-900"), title), - subtitle.map(st => p(cls("mt-1 max-w-2xl text-sm text-gray-500"), st)) - ), - div(cls("mt-6 sm:mt-5 space-y-6 sm:space-y-5"), content) - ) - - def label(labelText: String, forId: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.label( - cls("block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"), - forId.map(id => L.forId(id)), - labelText, - mods - ) - - def field( - label: Modifier[HtmlElement] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls( - "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5" - ), - label, - div(cls("mt-1 sm:mt-0 sm:col-span-2"), content) - ) - - def field( - id: String, - labelText: String, - input: HtmlElement, - help: Option[String] - ): HtmlElement = - field( - label(labelText, Some(id))() - )( - input.amend(idAttr(id)), - help.map(h => p(cls("mt-2 text-sm text-gray-500"), h)) - ) - - def form(mods: Modifier[HtmlElement]*)( - sections: Modifier[HtmlElement]* - )(actions: Modifier[HtmlElement]*): HtmlElement = - L.form( - cls("space-y-8 divide-y divide-gray-200"), - mods, - sections, - div( - cls("pt-5"), - div(cls("flex justify-end"), actions) - ) - ) - - def inlineForm( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.form(cls("flex space-x-4"), mods) - - def inputField( - id: String, - labelText: String, - placeholderText: Option[String] = None, - inputType: String = "text", - helpText: Option[String] = None - ): HtmlElement = - field( - id, - labelText, - input( - tpe(inputType), - cls( - "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6" - ), - placeholderText.map(placeholder(_)) - ), - helpText - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala deleted file mode 100644 index 52da07e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait HeadingsComponentsModule: - object headings: - def section(title: Node, actions: HtmlElement*): HtmlElement = - div( - cls("border-b border-gray-200 pb-5"), - div( - cls("sm:flex sm:items-center sm:justify-between"), - h3(cls("text-base font-semibold leading-6 text-gray-900"), title), - div( - cls("mt-3 flex sm:ml-4 sm:mt-0"), - actions match - case Nil => emptyMod - case first :: rest => - first :: rest.map(_.amend(cls("ml-3"))) - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala deleted file mode 100644 index c543150..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala +++ /dev/null @@ -1,112 +0,0 @@ -package works.iterative.ui.components.laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.CustomAttrs - -trait IconsModule: - object icons: - import svg.* - - object aria: - val hidden = CustomAttrs.svg.ariaHidden - - private def withDefault( - mods: Seq[Modifier[SvgElement]], - default: Modifier[SvgElement] - ): Modifier[SvgElement] = - if mods.isEmpty then default else mods - - def avatarPlaceholder(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-8 w-8"), - fill("none"), - stroke("currentColor"), - viewBox("0 0 24 24"), - xmlns("http://www.w3.org/2000/svg"), - aria.hidden(true), - path( - strokeLineCap("round"), - strokeLineJoin("round"), - strokeWidth("2"), - d( - "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - ) - - def close(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-2 w-2"), - stroke := "currentColor", - fill := "none", - viewBox := "0 0 8 8", - path( - strokeLineCap := "round", - strokeWidth := "1.5", - d := "M1 1l6 6m0-6L1 7" - ) - ) - - def `search-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" - ), - clipRule("evenodd") - ) - ) - - def `filter-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" - ), - clipRule("evenodd") - ) - ) - - def `document-chart-bar-outline`( - mods: Modifier[SvgElement]* - ): SvgElement = - svg( - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - strokeWidth := "1.5", - stroke := "currentColor", - withDefault(mods, cls := "h-6 w-6"), - path( - strokeLineCap := "round", - strokeLineJoin := "round", - d := "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25M9 16.5v.75m3-3v3M15 12v5.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" - ) - ) - - def `paper-clip-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-5 w-5"), - cls := "flex-shrink-0 text-gray-400", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z", - clipRule := "evenodd" - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala deleted file mode 100644 index 02cbdb6..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait LayoutModule: - object layout: - def card(content: Modifier[HtmlElement]*): HtmlElement = - div(cls("bg-white shadow px-4 py-5 sm:rounded-lg sm:p-6"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala deleted file mode 100644 index 10a322c..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala +++ /dev/null @@ -1,87 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.model.FileRef - -trait DetailComponentsModule: - self: IconsModule => - object details: - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]], - actions: Modifier[HtmlElement]* - )(fields: HtmlElement*): HtmlElement = - div( - div( - cls := "px-4 sm:px-0", - h3( - cls := "text-base font-semibold leading-7 text-gray-900", - title - ), - subtitle.map(st => - p( - cls := "mt-1 max-w-2xl text-sm leading-6 text-gray-500", - st - ) - ) - // TODO: actions - ), - div( - cls := "mt-6 border-t border-gray-100", - dl( - cls := "divide-y divide-gray-100", - fields - ) - ) - ) - - def field( - title: String, - content: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls := "px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0", - dt( - cls := "text-sm font-medium leading-6 text-gray-900", - title - ), - dd( - cls := "mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0", - content - ) - ) - - def files(fs: List[FileRef]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-100 rounded-md border border-gray-200", - fs.map(file) - ) - - def file(f: FileRef): HtmlElement = - li( - cls := "flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6", - div( - cls := "flex w-0 flex-1 items-center", - icons.`paper-clip-solid`(), - div( - cls := "ml-4 flex min-w-0 flex-1 gap-2", - span( - cls := "truncate font-medium", - f.name - ), - f.size.map(size => span(cls := "flex-shrink-0 text-gray-400", size)) - ) - ), - div( - cls := "ml-4 flex-shrink-0", - a( - href := "#", - cls := "font-medium text-indigo-600 hover:text-indigo-500", - "Uložit" - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala deleted file mode 100644 index eb6dd5f..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala +++ /dev/null @@ -1,101 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import org.scalajs.dom.html -import com.raquo.laminar.modifiers.KeyUpdater - -trait FormComponentsModule: - self: IconsModule => - - object forms: - - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls("space-y-6 sm:space-y-5"), - div( - h3(cls("text-lg leading-6 font-medium text-gray-900"), title), - subtitle.map(st => p(cls("mt-1 max-w-2xl text-sm text-gray-500"), st)) - ), - div(cls("mt-6 sm:mt-5 space-y-6 sm:space-y-5"), content) - ) - - def label(labelText: String, forId: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.label( - cls("block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"), - forId.map(id => L.forId(id)), - labelText, - mods - ) - - def field( - label: Modifier[HtmlElement] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls( - "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5" - ), - label, - div(cls("mt-1 sm:mt-0 sm:col-span-2"), content) - ) - - def field( - id: String, - labelText: String, - input: HtmlElement, - help: Option[String] - ): HtmlElement = - field( - label(labelText, Some(id))() - )( - input.amend(idAttr(id)), - help.map(h => p(cls("mt-2 text-sm text-gray-500"), h)) - ) - - def form(mods: Modifier[HtmlElement]*)( - sections: Modifier[HtmlElement]* - )(actions: Modifier[HtmlElement]*): HtmlElement = - L.form( - cls("space-y-8 divide-y divide-gray-200"), - mods, - sections, - div( - cls("pt-5"), - div(cls("flex justify-end"), actions) - ) - ) - - def inlineForm( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.form(cls("flex space-x-4"), mods) - - def inputField( - id: String, - labelText: String, - placeholderText: Option[String] = None, - inputType: String = "text", - helpText: Option[String] = None - ): HtmlElement = - field( - id, - labelText, - input( - tpe(inputType), - cls( - "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6" - ), - placeholderText.map(placeholder(_)) - ), - helpText - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala deleted file mode 100644 index 52da07e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait HeadingsComponentsModule: - object headings: - def section(title: Node, actions: HtmlElement*): HtmlElement = - div( - cls("border-b border-gray-200 pb-5"), - div( - cls("sm:flex sm:items-center sm:justify-between"), - h3(cls("text-base font-semibold leading-6 text-gray-900"), title), - div( - cls("mt-3 flex sm:ml-4 sm:mt-0"), - actions match - case Nil => emptyMod - case first :: rest => - first :: rest.map(_.amend(cls("ml-3"))) - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala deleted file mode 100644 index c543150..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala +++ /dev/null @@ -1,112 +0,0 @@ -package works.iterative.ui.components.laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.CustomAttrs - -trait IconsModule: - object icons: - import svg.* - - object aria: - val hidden = CustomAttrs.svg.ariaHidden - - private def withDefault( - mods: Seq[Modifier[SvgElement]], - default: Modifier[SvgElement] - ): Modifier[SvgElement] = - if mods.isEmpty then default else mods - - def avatarPlaceholder(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-8 w-8"), - fill("none"), - stroke("currentColor"), - viewBox("0 0 24 24"), - xmlns("http://www.w3.org/2000/svg"), - aria.hidden(true), - path( - strokeLineCap("round"), - strokeLineJoin("round"), - strokeWidth("2"), - d( - "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - ) - - def close(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-2 w-2"), - stroke := "currentColor", - fill := "none", - viewBox := "0 0 8 8", - path( - strokeLineCap := "round", - strokeWidth := "1.5", - d := "M1 1l6 6m0-6L1 7" - ) - ) - - def `search-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" - ), - clipRule("evenodd") - ) - ) - - def `filter-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" - ), - clipRule("evenodd") - ) - ) - - def `document-chart-bar-outline`( - mods: Modifier[SvgElement]* - ): SvgElement = - svg( - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - strokeWidth := "1.5", - stroke := "currentColor", - withDefault(mods, cls := "h-6 w-6"), - path( - strokeLineCap := "round", - strokeLineJoin := "round", - d := "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25M9 16.5v.75m3-3v3M15 12v5.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" - ) - ) - - def `paper-clip-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-5 w-5"), - cls := "flex-shrink-0 text-gray-400", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z", - clipRule := "evenodd" - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala deleted file mode 100644 index 02cbdb6..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait LayoutModule: - object layout: - def card(content: Modifier[HtmlElement]*): HtmlElement = - div(cls("bg-white shadow px-4 py-5 sm:rounded-lg sm:p-6"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala deleted file mode 100644 index b652da9..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala +++ /dev/null @@ -1,88 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom.html.Paragraph -import com.raquo.laminar.nodes.TextNode -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait ListComponentsModule: - self: BadgeComponentsModule => - - object list: - def label( - text: String, - color: ColorKind - ): HtmlElement = badges.pill(text, color) - - def item( - title: String, - subtitle: Option[String], - right: Modifier[HtmlElement] = emptyMod, - avatar: Option[Modifier[HtmlElement]] = None - ): LI = - li( - cls("group"), - div( - cls( - "bg-white relative px-6 py-5 flex items-center space-x-3 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-pink-500" - ), - avatar.map(a => - div( - cls("flex-shrink-0"), - div( - cls( - "rounded-full text-indigo-200 bg-indigo-600 flex items-center justify-center w-10 h-10" - ), - a - ) - ) - ), - div( - cls("flex-1 min-w-0"), - p( - cls("text-sm font-medium text-gray-900"), - title, - span(cls("float-right"), right) - ), - subtitle.map(st => - p( - cls("text-sm text-gray-500 truncate"), - st - ) - ) - ) - ) - ) - - def unordered( - children: Modifier[HtmlElement] - ): ReactiveHtmlElement[org.scalajs.dom.html.UList] = - ul( - cls("relative z-0 divide-y divide-gray-200"), - role("list"), - children - ) - - def listSection( - header: String, - list: HtmlElement - ): Div = - div( - cls("relative"), - div( - cls( - "z-10 sticky top-0 border-t border-b border-gray-200 bg-gray-50 px-6 py-1 text-sm font-medium text-gray-500" - ), - header - ), - list - ) - - def navigation(sections: Modifier[HtmlElement]): HtmlElement = - navTag( - cls("flex-1 min-h-0 overflow-y-auto"), - sections - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala deleted file mode 100644 index 10a322c..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala +++ /dev/null @@ -1,87 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.model.FileRef - -trait DetailComponentsModule: - self: IconsModule => - object details: - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]], - actions: Modifier[HtmlElement]* - )(fields: HtmlElement*): HtmlElement = - div( - div( - cls := "px-4 sm:px-0", - h3( - cls := "text-base font-semibold leading-7 text-gray-900", - title - ), - subtitle.map(st => - p( - cls := "mt-1 max-w-2xl text-sm leading-6 text-gray-500", - st - ) - ) - // TODO: actions - ), - div( - cls := "mt-6 border-t border-gray-100", - dl( - cls := "divide-y divide-gray-100", - fields - ) - ) - ) - - def field( - title: String, - content: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls := "px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0", - dt( - cls := "text-sm font-medium leading-6 text-gray-900", - title - ), - dd( - cls := "mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0", - content - ) - ) - - def files(fs: List[FileRef]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-100 rounded-md border border-gray-200", - fs.map(file) - ) - - def file(f: FileRef): HtmlElement = - li( - cls := "flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6", - div( - cls := "flex w-0 flex-1 items-center", - icons.`paper-clip-solid`(), - div( - cls := "ml-4 flex min-w-0 flex-1 gap-2", - span( - cls := "truncate font-medium", - f.name - ), - f.size.map(size => span(cls := "flex-shrink-0 text-gray-400", size)) - ) - ), - div( - cls := "ml-4 flex-shrink-0", - a( - href := "#", - cls := "font-medium text-indigo-600 hover:text-indigo-500", - "Uložit" - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala deleted file mode 100644 index eb6dd5f..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala +++ /dev/null @@ -1,101 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import org.scalajs.dom.html -import com.raquo.laminar.modifiers.KeyUpdater - -trait FormComponentsModule: - self: IconsModule => - - object forms: - - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls("space-y-6 sm:space-y-5"), - div( - h3(cls("text-lg leading-6 font-medium text-gray-900"), title), - subtitle.map(st => p(cls("mt-1 max-w-2xl text-sm text-gray-500"), st)) - ), - div(cls("mt-6 sm:mt-5 space-y-6 sm:space-y-5"), content) - ) - - def label(labelText: String, forId: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.label( - cls("block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"), - forId.map(id => L.forId(id)), - labelText, - mods - ) - - def field( - label: Modifier[HtmlElement] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls( - "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5" - ), - label, - div(cls("mt-1 sm:mt-0 sm:col-span-2"), content) - ) - - def field( - id: String, - labelText: String, - input: HtmlElement, - help: Option[String] - ): HtmlElement = - field( - label(labelText, Some(id))() - )( - input.amend(idAttr(id)), - help.map(h => p(cls("mt-2 text-sm text-gray-500"), h)) - ) - - def form(mods: Modifier[HtmlElement]*)( - sections: Modifier[HtmlElement]* - )(actions: Modifier[HtmlElement]*): HtmlElement = - L.form( - cls("space-y-8 divide-y divide-gray-200"), - mods, - sections, - div( - cls("pt-5"), - div(cls("flex justify-end"), actions) - ) - ) - - def inlineForm( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.form(cls("flex space-x-4"), mods) - - def inputField( - id: String, - labelText: String, - placeholderText: Option[String] = None, - inputType: String = "text", - helpText: Option[String] = None - ): HtmlElement = - field( - id, - labelText, - input( - tpe(inputType), - cls( - "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6" - ), - placeholderText.map(placeholder(_)) - ), - helpText - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala deleted file mode 100644 index 52da07e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait HeadingsComponentsModule: - object headings: - def section(title: Node, actions: HtmlElement*): HtmlElement = - div( - cls("border-b border-gray-200 pb-5"), - div( - cls("sm:flex sm:items-center sm:justify-between"), - h3(cls("text-base font-semibold leading-6 text-gray-900"), title), - div( - cls("mt-3 flex sm:ml-4 sm:mt-0"), - actions match - case Nil => emptyMod - case first :: rest => - first :: rest.map(_.amend(cls("ml-3"))) - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala deleted file mode 100644 index c543150..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala +++ /dev/null @@ -1,112 +0,0 @@ -package works.iterative.ui.components.laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.CustomAttrs - -trait IconsModule: - object icons: - import svg.* - - object aria: - val hidden = CustomAttrs.svg.ariaHidden - - private def withDefault( - mods: Seq[Modifier[SvgElement]], - default: Modifier[SvgElement] - ): Modifier[SvgElement] = - if mods.isEmpty then default else mods - - def avatarPlaceholder(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-8 w-8"), - fill("none"), - stroke("currentColor"), - viewBox("0 0 24 24"), - xmlns("http://www.w3.org/2000/svg"), - aria.hidden(true), - path( - strokeLineCap("round"), - strokeLineJoin("round"), - strokeWidth("2"), - d( - "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - ) - - def close(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-2 w-2"), - stroke := "currentColor", - fill := "none", - viewBox := "0 0 8 8", - path( - strokeLineCap := "round", - strokeWidth := "1.5", - d := "M1 1l6 6m0-6L1 7" - ) - ) - - def `search-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" - ), - clipRule("evenodd") - ) - ) - - def `filter-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" - ), - clipRule("evenodd") - ) - ) - - def `document-chart-bar-outline`( - mods: Modifier[SvgElement]* - ): SvgElement = - svg( - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - strokeWidth := "1.5", - stroke := "currentColor", - withDefault(mods, cls := "h-6 w-6"), - path( - strokeLineCap := "round", - strokeLineJoin := "round", - d := "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25M9 16.5v.75m3-3v3M15 12v5.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" - ) - ) - - def `paper-clip-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-5 w-5"), - cls := "flex-shrink-0 text-gray-400", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z", - clipRule := "evenodd" - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala deleted file mode 100644 index 02cbdb6..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait LayoutModule: - object layout: - def card(content: Modifier[HtmlElement]*): HtmlElement = - div(cls("bg-white shadow px-4 py-5 sm:rounded-lg sm:p-6"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala deleted file mode 100644 index b652da9..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala +++ /dev/null @@ -1,88 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom.html.Paragraph -import com.raquo.laminar.nodes.TextNode -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait ListComponentsModule: - self: BadgeComponentsModule => - - object list: - def label( - text: String, - color: ColorKind - ): HtmlElement = badges.pill(text, color) - - def item( - title: String, - subtitle: Option[String], - right: Modifier[HtmlElement] = emptyMod, - avatar: Option[Modifier[HtmlElement]] = None - ): LI = - li( - cls("group"), - div( - cls( - "bg-white relative px-6 py-5 flex items-center space-x-3 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-pink-500" - ), - avatar.map(a => - div( - cls("flex-shrink-0"), - div( - cls( - "rounded-full text-indigo-200 bg-indigo-600 flex items-center justify-center w-10 h-10" - ), - a - ) - ) - ), - div( - cls("flex-1 min-w-0"), - p( - cls("text-sm font-medium text-gray-900"), - title, - span(cls("float-right"), right) - ), - subtitle.map(st => - p( - cls("text-sm text-gray-500 truncate"), - st - ) - ) - ) - ) - ) - - def unordered( - children: Modifier[HtmlElement] - ): ReactiveHtmlElement[org.scalajs.dom.html.UList] = - ul( - cls("relative z-0 divide-y divide-gray-200"), - role("list"), - children - ) - - def listSection( - header: String, - list: HtmlElement - ): Div = - div( - cls("relative"), - div( - cls( - "z-10 sticky top-0 border-t border-b border-gray-200 bg-gray-50 px-6 py-1 text-sm font-medium text-gray-500" - ), - header - ), - list - ) - - def navigation(sections: Modifier[HtmlElement]): HtmlElement = - navTag( - cls("flex-1 min-h-0 overflow-y-auto"), - sections - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala deleted file mode 100644 index 6027404..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala +++ /dev/null @@ -1,54 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait ModalComponentsModule: - object modal: - def modalDialog( - content: HtmlElement, - closeMod: Modifier[HtmlElement] - ): HtmlElement = - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")), - closeMod - ) - ) - - div( - cls("fixed inset-0 z-20 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-7xl sm:align-middle" - ), - content - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala deleted file mode 100644 index 10a322c..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala +++ /dev/null @@ -1,87 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.model.FileRef - -trait DetailComponentsModule: - self: IconsModule => - object details: - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]], - actions: Modifier[HtmlElement]* - )(fields: HtmlElement*): HtmlElement = - div( - div( - cls := "px-4 sm:px-0", - h3( - cls := "text-base font-semibold leading-7 text-gray-900", - title - ), - subtitle.map(st => - p( - cls := "mt-1 max-w-2xl text-sm leading-6 text-gray-500", - st - ) - ) - // TODO: actions - ), - div( - cls := "mt-6 border-t border-gray-100", - dl( - cls := "divide-y divide-gray-100", - fields - ) - ) - ) - - def field( - title: String, - content: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls := "px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0", - dt( - cls := "text-sm font-medium leading-6 text-gray-900", - title - ), - dd( - cls := "mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0", - content - ) - ) - - def files(fs: List[FileRef]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-100 rounded-md border border-gray-200", - fs.map(file) - ) - - def file(f: FileRef): HtmlElement = - li( - cls := "flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6", - div( - cls := "flex w-0 flex-1 items-center", - icons.`paper-clip-solid`(), - div( - cls := "ml-4 flex min-w-0 flex-1 gap-2", - span( - cls := "truncate font-medium", - f.name - ), - f.size.map(size => span(cls := "flex-shrink-0 text-gray-400", size)) - ) - ), - div( - cls := "ml-4 flex-shrink-0", - a( - href := "#", - cls := "font-medium text-indigo-600 hover:text-indigo-500", - "Uložit" - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala deleted file mode 100644 index eb6dd5f..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala +++ /dev/null @@ -1,101 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import org.scalajs.dom.html -import com.raquo.laminar.modifiers.KeyUpdater - -trait FormComponentsModule: - self: IconsModule => - - object forms: - - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls("space-y-6 sm:space-y-5"), - div( - h3(cls("text-lg leading-6 font-medium text-gray-900"), title), - subtitle.map(st => p(cls("mt-1 max-w-2xl text-sm text-gray-500"), st)) - ), - div(cls("mt-6 sm:mt-5 space-y-6 sm:space-y-5"), content) - ) - - def label(labelText: String, forId: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.label( - cls("block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"), - forId.map(id => L.forId(id)), - labelText, - mods - ) - - def field( - label: Modifier[HtmlElement] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls( - "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5" - ), - label, - div(cls("mt-1 sm:mt-0 sm:col-span-2"), content) - ) - - def field( - id: String, - labelText: String, - input: HtmlElement, - help: Option[String] - ): HtmlElement = - field( - label(labelText, Some(id))() - )( - input.amend(idAttr(id)), - help.map(h => p(cls("mt-2 text-sm text-gray-500"), h)) - ) - - def form(mods: Modifier[HtmlElement]*)( - sections: Modifier[HtmlElement]* - )(actions: Modifier[HtmlElement]*): HtmlElement = - L.form( - cls("space-y-8 divide-y divide-gray-200"), - mods, - sections, - div( - cls("pt-5"), - div(cls("flex justify-end"), actions) - ) - ) - - def inlineForm( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.form(cls("flex space-x-4"), mods) - - def inputField( - id: String, - labelText: String, - placeholderText: Option[String] = None, - inputType: String = "text", - helpText: Option[String] = None - ): HtmlElement = - field( - id, - labelText, - input( - tpe(inputType), - cls( - "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6" - ), - placeholderText.map(placeholder(_)) - ), - helpText - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala deleted file mode 100644 index 52da07e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait HeadingsComponentsModule: - object headings: - def section(title: Node, actions: HtmlElement*): HtmlElement = - div( - cls("border-b border-gray-200 pb-5"), - div( - cls("sm:flex sm:items-center sm:justify-between"), - h3(cls("text-base font-semibold leading-6 text-gray-900"), title), - div( - cls("mt-3 flex sm:ml-4 sm:mt-0"), - actions match - case Nil => emptyMod - case first :: rest => - first :: rest.map(_.amend(cls("ml-3"))) - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala deleted file mode 100644 index c543150..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala +++ /dev/null @@ -1,112 +0,0 @@ -package works.iterative.ui.components.laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.CustomAttrs - -trait IconsModule: - object icons: - import svg.* - - object aria: - val hidden = CustomAttrs.svg.ariaHidden - - private def withDefault( - mods: Seq[Modifier[SvgElement]], - default: Modifier[SvgElement] - ): Modifier[SvgElement] = - if mods.isEmpty then default else mods - - def avatarPlaceholder(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-8 w-8"), - fill("none"), - stroke("currentColor"), - viewBox("0 0 24 24"), - xmlns("http://www.w3.org/2000/svg"), - aria.hidden(true), - path( - strokeLineCap("round"), - strokeLineJoin("round"), - strokeWidth("2"), - d( - "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - ) - - def close(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-2 w-2"), - stroke := "currentColor", - fill := "none", - viewBox := "0 0 8 8", - path( - strokeLineCap := "round", - strokeWidth := "1.5", - d := "M1 1l6 6m0-6L1 7" - ) - ) - - def `search-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" - ), - clipRule("evenodd") - ) - ) - - def `filter-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" - ), - clipRule("evenodd") - ) - ) - - def `document-chart-bar-outline`( - mods: Modifier[SvgElement]* - ): SvgElement = - svg( - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - strokeWidth := "1.5", - stroke := "currentColor", - withDefault(mods, cls := "h-6 w-6"), - path( - strokeLineCap := "round", - strokeLineJoin := "round", - d := "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25M9 16.5v.75m3-3v3M15 12v5.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" - ) - ) - - def `paper-clip-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-5 w-5"), - cls := "flex-shrink-0 text-gray-400", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z", - clipRule := "evenodd" - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala deleted file mode 100644 index 02cbdb6..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait LayoutModule: - object layout: - def card(content: Modifier[HtmlElement]*): HtmlElement = - div(cls("bg-white shadow px-4 py-5 sm:rounded-lg sm:p-6"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala deleted file mode 100644 index b652da9..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala +++ /dev/null @@ -1,88 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom.html.Paragraph -import com.raquo.laminar.nodes.TextNode -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait ListComponentsModule: - self: BadgeComponentsModule => - - object list: - def label( - text: String, - color: ColorKind - ): HtmlElement = badges.pill(text, color) - - def item( - title: String, - subtitle: Option[String], - right: Modifier[HtmlElement] = emptyMod, - avatar: Option[Modifier[HtmlElement]] = None - ): LI = - li( - cls("group"), - div( - cls( - "bg-white relative px-6 py-5 flex items-center space-x-3 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-pink-500" - ), - avatar.map(a => - div( - cls("flex-shrink-0"), - div( - cls( - "rounded-full text-indigo-200 bg-indigo-600 flex items-center justify-center w-10 h-10" - ), - a - ) - ) - ), - div( - cls("flex-1 min-w-0"), - p( - cls("text-sm font-medium text-gray-900"), - title, - span(cls("float-right"), right) - ), - subtitle.map(st => - p( - cls("text-sm text-gray-500 truncate"), - st - ) - ) - ) - ) - ) - - def unordered( - children: Modifier[HtmlElement] - ): ReactiveHtmlElement[org.scalajs.dom.html.UList] = - ul( - cls("relative z-0 divide-y divide-gray-200"), - role("list"), - children - ) - - def listSection( - header: String, - list: HtmlElement - ): Div = - div( - cls("relative"), - div( - cls( - "z-10 sticky top-0 border-t border-b border-gray-200 bg-gray-50 px-6 py-1 text-sm font-medium text-gray-500" - ), - header - ), - list - ) - - def navigation(sections: Modifier[HtmlElement]): HtmlElement = - navTag( - cls("flex-1 min-h-0 overflow-y-auto"), - sections - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala deleted file mode 100644 index 6027404..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala +++ /dev/null @@ -1,54 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait ModalComponentsModule: - object modal: - def modalDialog( - content: HtmlElement, - closeMod: Modifier[HtmlElement] - ): HtmlElement = - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")), - closeMod - ) - ) - - div( - cls("fixed inset-0 z-20 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-7xl sm:align-middle" - ), - content - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/PageComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/PageComponentsModule.scala deleted file mode 100644 index d5a422e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/PageComponentsModule.scala +++ /dev/null @@ -1,49 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait PageComponentsModule: - - object page: - def container( - children: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls("max-w-7xl mx-auto h-full px-4 sm:px-6 lg:px-8 overflow-y-auto"), - children - ) - - def singleColumn( - header: Modifier[HtmlElement] - )(children: Modifier[HtmlElement]*): HtmlElement = - div( - cls("p-8 bg-gray-100 h-full"), - header, - children - ) - - def pageHeader( - title: Modifier[HtmlElement], - right: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] = None - ): HtmlElement = - div( - cls("pb-5 border-b border-gray-200"), - div(cls("float-right"), right), - h1( - cls("text-2xl leading-6 font-medium text-gray-900"), - title - ), - subtitle.map( - p( - cls("text-sm font-medium text-gray-500"), - _ - ) - ) - ) - - def clickable: Modifier[HtmlElement] = - cls("text-sm font-medium text-indigo-600 hover:text-indigo-400") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala deleted file mode 100644 index 10a322c..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala +++ /dev/null @@ -1,87 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.model.FileRef - -trait DetailComponentsModule: - self: IconsModule => - object details: - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]], - actions: Modifier[HtmlElement]* - )(fields: HtmlElement*): HtmlElement = - div( - div( - cls := "px-4 sm:px-0", - h3( - cls := "text-base font-semibold leading-7 text-gray-900", - title - ), - subtitle.map(st => - p( - cls := "mt-1 max-w-2xl text-sm leading-6 text-gray-500", - st - ) - ) - // TODO: actions - ), - div( - cls := "mt-6 border-t border-gray-100", - dl( - cls := "divide-y divide-gray-100", - fields - ) - ) - ) - - def field( - title: String, - content: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls := "px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0", - dt( - cls := "text-sm font-medium leading-6 text-gray-900", - title - ), - dd( - cls := "mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0", - content - ) - ) - - def files(fs: List[FileRef]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-100 rounded-md border border-gray-200", - fs.map(file) - ) - - def file(f: FileRef): HtmlElement = - li( - cls := "flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6", - div( - cls := "flex w-0 flex-1 items-center", - icons.`paper-clip-solid`(), - div( - cls := "ml-4 flex min-w-0 flex-1 gap-2", - span( - cls := "truncate font-medium", - f.name - ), - f.size.map(size => span(cls := "flex-shrink-0 text-gray-400", size)) - ) - ), - div( - cls := "ml-4 flex-shrink-0", - a( - href := "#", - cls := "font-medium text-indigo-600 hover:text-indigo-500", - "Uložit" - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala deleted file mode 100644 index eb6dd5f..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala +++ /dev/null @@ -1,101 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import org.scalajs.dom.html -import com.raquo.laminar.modifiers.KeyUpdater - -trait FormComponentsModule: - self: IconsModule => - - object forms: - - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls("space-y-6 sm:space-y-5"), - div( - h3(cls("text-lg leading-6 font-medium text-gray-900"), title), - subtitle.map(st => p(cls("mt-1 max-w-2xl text-sm text-gray-500"), st)) - ), - div(cls("mt-6 sm:mt-5 space-y-6 sm:space-y-5"), content) - ) - - def label(labelText: String, forId: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.label( - cls("block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"), - forId.map(id => L.forId(id)), - labelText, - mods - ) - - def field( - label: Modifier[HtmlElement] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls( - "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5" - ), - label, - div(cls("mt-1 sm:mt-0 sm:col-span-2"), content) - ) - - def field( - id: String, - labelText: String, - input: HtmlElement, - help: Option[String] - ): HtmlElement = - field( - label(labelText, Some(id))() - )( - input.amend(idAttr(id)), - help.map(h => p(cls("mt-2 text-sm text-gray-500"), h)) - ) - - def form(mods: Modifier[HtmlElement]*)( - sections: Modifier[HtmlElement]* - )(actions: Modifier[HtmlElement]*): HtmlElement = - L.form( - cls("space-y-8 divide-y divide-gray-200"), - mods, - sections, - div( - cls("pt-5"), - div(cls("flex justify-end"), actions) - ) - ) - - def inlineForm( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.form(cls("flex space-x-4"), mods) - - def inputField( - id: String, - labelText: String, - placeholderText: Option[String] = None, - inputType: String = "text", - helpText: Option[String] = None - ): HtmlElement = - field( - id, - labelText, - input( - tpe(inputType), - cls( - "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6" - ), - placeholderText.map(placeholder(_)) - ), - helpText - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala deleted file mode 100644 index 52da07e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait HeadingsComponentsModule: - object headings: - def section(title: Node, actions: HtmlElement*): HtmlElement = - div( - cls("border-b border-gray-200 pb-5"), - div( - cls("sm:flex sm:items-center sm:justify-between"), - h3(cls("text-base font-semibold leading-6 text-gray-900"), title), - div( - cls("mt-3 flex sm:ml-4 sm:mt-0"), - actions match - case Nil => emptyMod - case first :: rest => - first :: rest.map(_.amend(cls("ml-3"))) - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala deleted file mode 100644 index c543150..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala +++ /dev/null @@ -1,112 +0,0 @@ -package works.iterative.ui.components.laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.CustomAttrs - -trait IconsModule: - object icons: - import svg.* - - object aria: - val hidden = CustomAttrs.svg.ariaHidden - - private def withDefault( - mods: Seq[Modifier[SvgElement]], - default: Modifier[SvgElement] - ): Modifier[SvgElement] = - if mods.isEmpty then default else mods - - def avatarPlaceholder(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-8 w-8"), - fill("none"), - stroke("currentColor"), - viewBox("0 0 24 24"), - xmlns("http://www.w3.org/2000/svg"), - aria.hidden(true), - path( - strokeLineCap("round"), - strokeLineJoin("round"), - strokeWidth("2"), - d( - "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - ) - - def close(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-2 w-2"), - stroke := "currentColor", - fill := "none", - viewBox := "0 0 8 8", - path( - strokeLineCap := "round", - strokeWidth := "1.5", - d := "M1 1l6 6m0-6L1 7" - ) - ) - - def `search-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" - ), - clipRule("evenodd") - ) - ) - - def `filter-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" - ), - clipRule("evenodd") - ) - ) - - def `document-chart-bar-outline`( - mods: Modifier[SvgElement]* - ): SvgElement = - svg( - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - strokeWidth := "1.5", - stroke := "currentColor", - withDefault(mods, cls := "h-6 w-6"), - path( - strokeLineCap := "round", - strokeLineJoin := "round", - d := "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25M9 16.5v.75m3-3v3M15 12v5.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" - ) - ) - - def `paper-clip-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-5 w-5"), - cls := "flex-shrink-0 text-gray-400", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z", - clipRule := "evenodd" - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala deleted file mode 100644 index 02cbdb6..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait LayoutModule: - object layout: - def card(content: Modifier[HtmlElement]*): HtmlElement = - div(cls("bg-white shadow px-4 py-5 sm:rounded-lg sm:p-6"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala deleted file mode 100644 index b652da9..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala +++ /dev/null @@ -1,88 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom.html.Paragraph -import com.raquo.laminar.nodes.TextNode -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait ListComponentsModule: - self: BadgeComponentsModule => - - object list: - def label( - text: String, - color: ColorKind - ): HtmlElement = badges.pill(text, color) - - def item( - title: String, - subtitle: Option[String], - right: Modifier[HtmlElement] = emptyMod, - avatar: Option[Modifier[HtmlElement]] = None - ): LI = - li( - cls("group"), - div( - cls( - "bg-white relative px-6 py-5 flex items-center space-x-3 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-pink-500" - ), - avatar.map(a => - div( - cls("flex-shrink-0"), - div( - cls( - "rounded-full text-indigo-200 bg-indigo-600 flex items-center justify-center w-10 h-10" - ), - a - ) - ) - ), - div( - cls("flex-1 min-w-0"), - p( - cls("text-sm font-medium text-gray-900"), - title, - span(cls("float-right"), right) - ), - subtitle.map(st => - p( - cls("text-sm text-gray-500 truncate"), - st - ) - ) - ) - ) - ) - - def unordered( - children: Modifier[HtmlElement] - ): ReactiveHtmlElement[org.scalajs.dom.html.UList] = - ul( - cls("relative z-0 divide-y divide-gray-200"), - role("list"), - children - ) - - def listSection( - header: String, - list: HtmlElement - ): Div = - div( - cls("relative"), - div( - cls( - "z-10 sticky top-0 border-t border-b border-gray-200 bg-gray-50 px-6 py-1 text-sm font-medium text-gray-500" - ), - header - ), - list - ) - - def navigation(sections: Modifier[HtmlElement]): HtmlElement = - navTag( - cls("flex-1 min-h-0 overflow-y-auto"), - sections - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala deleted file mode 100644 index 6027404..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala +++ /dev/null @@ -1,54 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait ModalComponentsModule: - object modal: - def modalDialog( - content: HtmlElement, - closeMod: Modifier[HtmlElement] - ): HtmlElement = - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")), - closeMod - ) - ) - - div( - cls("fixed inset-0 z-20 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-7xl sm:align-middle" - ), - content - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/PageComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/PageComponentsModule.scala deleted file mode 100644 index d5a422e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/PageComponentsModule.scala +++ /dev/null @@ -1,49 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait PageComponentsModule: - - object page: - def container( - children: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls("max-w-7xl mx-auto h-full px-4 sm:px-6 lg:px-8 overflow-y-auto"), - children - ) - - def singleColumn( - header: Modifier[HtmlElement] - )(children: Modifier[HtmlElement]*): HtmlElement = - div( - cls("p-8 bg-gray-100 h-full"), - header, - children - ) - - def pageHeader( - title: Modifier[HtmlElement], - right: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] = None - ): HtmlElement = - div( - cls("pb-5 border-b border-gray-200"), - div(cls("float-right"), right), - h1( - cls("text-2xl leading-6 font-medium text-gray-900"), - title - ), - subtitle.map( - p( - cls("text-sm font-medium text-gray-500"), - _ - ) - ) - ) - - def clickable: Modifier[HtmlElement] = - cls("text-sm font-medium text-indigo-600 hover:text-indigo-400") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/TableComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/TableComponentsModule.scala deleted file mode 100644 index 417ea56..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/TableComponentsModule.scala +++ /dev/null @@ -1,86 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} - -trait TableComponentsModule: - - object tables: - - def tableSection( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] = None, - actions: Modifier[HtmlElement]* - )( - table: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls("px-4 sm:px-6 lg:px-8"), - div( - cls("sm:flex sm:items-center"), - div( - cls("sm:flex-auto"), - h1(cls("text-base font-semibold leading-6 text-gray-900"), title), - subtitle.map(st => p(cls("mt-2 text-sm text-gray-700"), st)) - ), - div(cls("mt-4 sm:ml-16 sm:mt-0 sm:flex-none"), actions) - ), - div( - cls("mt-8 flow-root"), - div( - cls("-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"), - div( - cls("inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"), - table - ) - ) - ) - ) - - def simpleTable(header: Modifier[HtmlElement]*)( - body: Modifier[HtmlElement]* - ): HtmlElement = - table( - cls("min-w-full divide-y divide-gray-300"), - thead(header), - tbody(cls("divide-y divide-gray-200"), body) - ) - - def headerRow( - mods: Modifier[HtmlElement]* - )(cells: HtmlElement*): HtmlElement = - tr( - cells.zipWithIndex.map((c, i) => - if i == 0 then c.amend(cls("py-3.5 pl-4 pr-3 sm:pl-0")) - else if i == cells.length - 1 then - c.amend(cls("py-3.5 pr-4 pl-3 sm:pr-0")) - else c.amend(cls("py-3.5 px-3")) - ) - ) - - def dataRow( - mods: Modifier[HtmlElement]* - )(cells: HtmlElement*): HtmlElement = - tr( - mods, - cells.zipWithIndex.map((c, i) => - if i == 0 then c.amend(cls("py-4 pl-4 pr-3 sm:pl-0")) - else if i == cells.length - 1 then - c.amend(cls("py-4 pr-4 pl-3 sm:pr-0")) - else c.amend(cls("py-4 px-3")) - ) - ) - - def headerCell(content: Modifier[HtmlElement]): HtmlElement = - th( - cls("text-left text-sm font-semibold text-gray-900"), - content - ) - - def dataCell(content: Modifier[HtmlElement]): HtmlElement = - td( - cls("whitespace-nowrap text-sm text-gray-500"), - content - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 12d01ae..432968c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -3,50 +3,47 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular -import works.iterative.ui.components.laminar.tailwind.ui.TableComponentsModule import works.iterative.core.UserMessage import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given import works.iterative.ui.components.tailwind.ComponentContext +import com.raquo.laminar.nodes.ReactiveHtmlElement +import org.scalajs.dom.html trait HtmlTableBuilderModule: - def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] - trait HtmlTableBuilder[A]: - def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] - def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = - dataRowMod((a, _) => mod(a)) + trait TableUIFactory: + def table(headerRows: ReactiveHtmlElement[html.TableRow]*)( + bodyRows: ReactiveHtmlElement[html.TableRow]* + ): ReactiveHtmlElement[html.Table] + def headerRow(mod: HtmlMod)( + headerCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def dataRow(mod: HtmlMod)( + dataCells: ReactiveHtmlElement[html.TableCell]* + ): ReactiveHtmlElement[html.TableRow] + def headerCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] + def dataCell(content: HtmlMod): ReactiveHtmlElement[html.TableCell] - def headerCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] - def headerCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - headerCellMod(_ => mod) - - def dataCellMod(mod: (String, A) => HtmlMod): HtmlTableBuilder[A] - def dataCellMod(mod: HtmlMod): HtmlTableBuilder[A] = - dataCellMod((_, _) => mod) - def dataCellMod(mod: String => HtmlMod): HtmlTableBuilder[A] = - dataCellMod((s, _) => mod(s)) - - def build: HtmlElement - -trait HtmlTableBuilderModuleImpl(using resolver: TableHeaderResolver) - extends HtmlTableBuilderModule: - self: TableComponentsModule => + def tableHeaderResolver: TableHeaderResolver + def tableUIFactory: TableUIFactory def buildTable[A: HtmlTabular](data: List[A]): HtmlTableBuilder[A] = - new HtmlTableBuilderImpl[A](data) + HtmlTableBuilder[A](data) - case class HtmlTableBuilderImpl[A: HtmlTabular]( + case class HtmlTableBuilder[A: HtmlTabular]( data: List[A], headerRowMod: HtmlMod = emptyMod, dataRowMod: (A, Int) => HtmlMod = (_: A, _) => emptyMod, headerCellMod: String => HtmlMod = _ => emptyMod, dataCellMod: (String, A) => HtmlMod = (_, _: A) => emptyMod - ) extends HtmlTableBuilder[A]: + ): def headerRowMod(mod: HtmlMod): HtmlTableBuilder[A] = copy(headerRowMod = mod) + def dataRowMod(mod: A => HtmlMod): HtmlTableBuilder[A] = + copy(dataRowMod = (a, _) => mod(a)) + def dataRowMod(mod: (A, Int) => HtmlMod): HtmlTableBuilder[A] = copy(dataRowMod = mod) @@ -58,23 +55,22 @@ def build: HtmlElement = val tab = summon[HtmlTabular[A]] - tables.simpleTable( - tables.headerRow(headerRowMod)( + tableUIFactory.table( + tableUIFactory.headerRow(headerRowMod)( tab.columns.map(_.name).map { n => - tables - .headerCell( - Seq[HtmlMod](headerCellMod(n), resolver(n)) - ) + tableUIFactory.headerCell( + Seq[HtmlMod](headerCellMod(n), tableHeaderResolver(n)) + ) }* ) )( data.zipWithIndex.map((d, idx) => - tables.dataRow(dataRowMod(d, idx))( + tableUIFactory.dataRow(dataRowMod(d, idx))( tab.columns .map(c => c.name -> c.get(d)) .map { (n, v) => - tables.dataCell(Seq(v, dataCellMod(n, d))) + tableUIFactory.dataCell(Seq(v, dataCellMod(n, d))) }* ) - ) + )* ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala deleted file mode 100644 index 16ca01b..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/BadgeComponentsModule.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait BadgeComponentsModule: - - object badges: - def pill(name: String, color: ColorKind): HtmlElement = - p( - cls( - "px-2 inline-flex text-xs leading-5 font-semibold rounded-full" - ), - color(800).text, - color(100).bg, - name - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala deleted file mode 100644 index ebb2968..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ButtonComponentsModule.scala +++ /dev/null @@ -1,62 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonComponentsModule: - - object buttons: - - private inline def srHelp(text: String): Modifier[HtmlElement] = - span(cls := "sr-only", text) - - def primaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "submit" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls := "disabled:bg-indigo-300 ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - icon, - text, - mods - ) - - def secondaryButton( - id: String, - text: Modifier[HtmlElement], - icon: Option[SvgElement] = None, - buttonType: String = "button" - )(mods: Modifier[HtmlElement]*): HtmlElement = - button( - tpe(buttonType), - cls( - "ml-2 bg-white inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" - ), - icon, - text, - mods - ) - - def inlineButton(id: String, icon: SvgElement)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "ml-1 inline-flex h-4 w-4 flex-shrink-0 rounded-full p-1 text-gray-400 hover:bg-gray-200 hover:text-gray-500", - srHelp(id), - icon - ) - - def iconButton(id: String, icon: SvgElement, srText: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - button( - tpe := "button", - cls := "inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500", - icon, - srText.map(srHelp(_)) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala deleted file mode 100644 index 10a322c..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/DetailComponentsModule.scala +++ /dev/null @@ -1,87 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.model.FileRef - -trait DetailComponentsModule: - self: IconsModule => - object details: - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]], - actions: Modifier[HtmlElement]* - )(fields: HtmlElement*): HtmlElement = - div( - div( - cls := "px-4 sm:px-0", - h3( - cls := "text-base font-semibold leading-7 text-gray-900", - title - ), - subtitle.map(st => - p( - cls := "mt-1 max-w-2xl text-sm leading-6 text-gray-500", - st - ) - ) - // TODO: actions - ), - div( - cls := "mt-6 border-t border-gray-100", - dl( - cls := "divide-y divide-gray-100", - fields - ) - ) - ) - - def field( - title: String, - content: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls := "px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0", - dt( - cls := "text-sm font-medium leading-6 text-gray-900", - title - ), - dd( - cls := "mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0", - content - ) - ) - - def files(fs: List[FileRef]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-100 rounded-md border border-gray-200", - fs.map(file) - ) - - def file(f: FileRef): HtmlElement = - li( - cls := "flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6", - div( - cls := "flex w-0 flex-1 items-center", - icons.`paper-clip-solid`(), - div( - cls := "ml-4 flex min-w-0 flex-1 gap-2", - span( - cls := "truncate font-medium", - f.name - ), - f.size.map(size => span(cls := "flex-shrink-0 text-gray-400", size)) - ) - ), - div( - cls := "ml-4 flex-shrink-0", - a( - href := "#", - cls := "font-medium text-indigo-600 hover:text-indigo-500", - "Uložit" - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala deleted file mode 100644 index eb6dd5f..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/FormComponentsModule.scala +++ /dev/null @@ -1,101 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import org.scalajs.dom.html -import com.raquo.laminar.modifiers.KeyUpdater - -trait FormComponentsModule: - self: IconsModule => - - object forms: - - def section( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls("space-y-6 sm:space-y-5"), - div( - h3(cls("text-lg leading-6 font-medium text-gray-900"), title), - subtitle.map(st => p(cls("mt-1 max-w-2xl text-sm text-gray-500"), st)) - ), - div(cls("mt-6 sm:mt-5 space-y-6 sm:space-y-5"), content) - ) - - def label(labelText: String, forId: Option[String] = None)( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.label( - cls("block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"), - forId.map(id => L.forId(id)), - labelText, - mods - ) - - def field( - label: Modifier[HtmlElement] - )(content: Modifier[HtmlElement]*): HtmlElement = - div( - cls( - "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5" - ), - label, - div(cls("mt-1 sm:mt-0 sm:col-span-2"), content) - ) - - def field( - id: String, - labelText: String, - input: HtmlElement, - help: Option[String] - ): HtmlElement = - field( - label(labelText, Some(id))() - )( - input.amend(idAttr(id)), - help.map(h => p(cls("mt-2 text-sm text-gray-500"), h)) - ) - - def form(mods: Modifier[HtmlElement]*)( - sections: Modifier[HtmlElement]* - )(actions: Modifier[HtmlElement]*): HtmlElement = - L.form( - cls("space-y-8 divide-y divide-gray-200"), - mods, - sections, - div( - cls("pt-5"), - div(cls("flex justify-end"), actions) - ) - ) - - def inlineForm( - mods: Modifier[HtmlElement]* - ): HtmlElement = - L.form(cls("flex space-x-4"), mods) - - def inputField( - id: String, - labelText: String, - placeholderText: Option[String] = None, - inputType: String = "text", - helpText: Option[String] = None - ): HtmlElement = - field( - id, - labelText, - input( - tpe(inputType), - cls( - "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6" - ), - placeholderText.map(placeholder(_)) - ), - helpText - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala deleted file mode 100644 index 52da07e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/HeadingsComponentsModule.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait HeadingsComponentsModule: - object headings: - def section(title: Node, actions: HtmlElement*): HtmlElement = - div( - cls("border-b border-gray-200 pb-5"), - div( - cls("sm:flex sm:items-center sm:justify-between"), - h3(cls("text-base font-semibold leading-6 text-gray-900"), title), - div( - cls("mt-3 flex sm:ml-4 sm:mt-0"), - actions match - case Nil => emptyMod - case first :: rest => - first :: rest.map(_.amend(cls("ml-3"))) - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala deleted file mode 100644 index c543150..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/IconsModule.scala +++ /dev/null @@ -1,112 +0,0 @@ -package works.iterative.ui.components.laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.CustomAttrs - -trait IconsModule: - object icons: - import svg.* - - object aria: - val hidden = CustomAttrs.svg.ariaHidden - - private def withDefault( - mods: Seq[Modifier[SvgElement]], - default: Modifier[SvgElement] - ): Modifier[SvgElement] = - if mods.isEmpty then default else mods - - def avatarPlaceholder(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-8 w-8"), - fill("none"), - stroke("currentColor"), - viewBox("0 0 24 24"), - xmlns("http://www.w3.org/2000/svg"), - aria.hidden(true), - path( - strokeLineCap("round"), - strokeLineJoin("round"), - strokeWidth("2"), - d( - "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - ) - - def close(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-2 w-2"), - stroke := "currentColor", - fill := "none", - viewBox := "0 0 8 8", - path( - strokeLineCap := "round", - strokeWidth := "1.5", - d := "M1 1l6 6m0-6L1 7" - ) - ) - - def `search-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" - ), - clipRule("evenodd") - ) - ) - - def `filter-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls("h-5 w-5 text-gray-400")), - xmlns("http://www.w3.org/2000/svg"), - viewBox("0 0 20 20"), - fill("currentColor"), - aria.hidden(true), - path( - fillRule("evenodd"), - d( - "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" - ), - clipRule("evenodd") - ) - ) - - def `document-chart-bar-outline`( - mods: Modifier[SvgElement]* - ): SvgElement = - svg( - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - strokeWidth := "1.5", - stroke := "currentColor", - withDefault(mods, cls := "h-6 w-6"), - path( - strokeLineCap := "round", - strokeLineJoin := "round", - d := "M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25M9 16.5v.75m3-3v3M15 12v5.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" - ) - ) - - def `paper-clip-solid`(mods: Modifier[SvgElement]*): SvgElement = - svg( - withDefault(mods, cls := "h-5 w-5"), - cls := "flex-shrink-0 text-gray-400", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M15.621 4.379a3 3 0 00-4.242 0l-7 7a3 3 0 004.241 4.243h.001l.497-.5a.75.75 0 011.064 1.057l-.498.501-.002.002a4.5 4.5 0 01-6.364-6.364l7-7a4.5 4.5 0 016.368 6.36l-3.455 3.553A2.625 2.625 0 119.52 9.52l3.45-3.451a.75.75 0 111.061 1.06l-3.45 3.451a1.125 1.125 0 001.587 1.595l3.454-3.553a3 3 0 000-4.242z", - clipRule := "evenodd" - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala deleted file mode 100644 index 02cbdb6..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/LayoutModule.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait LayoutModule: - object layout: - def card(content: Modifier[HtmlElement]*): HtmlElement = - div(cls("bg-white shadow px-4 py-5 sm:rounded-lg sm:p-6"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala deleted file mode 100644 index b652da9..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ListComponentsModule.scala +++ /dev/null @@ -1,88 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom.html.Paragraph -import com.raquo.laminar.nodes.TextNode -import works.iterative.ui.components.laminar.tailwind.color.ColorKind - -trait ListComponentsModule: - self: BadgeComponentsModule => - - object list: - def label( - text: String, - color: ColorKind - ): HtmlElement = badges.pill(text, color) - - def item( - title: String, - subtitle: Option[String], - right: Modifier[HtmlElement] = emptyMod, - avatar: Option[Modifier[HtmlElement]] = None - ): LI = - li( - cls("group"), - div( - cls( - "bg-white relative px-6 py-5 flex items-center space-x-3 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-pink-500" - ), - avatar.map(a => - div( - cls("flex-shrink-0"), - div( - cls( - "rounded-full text-indigo-200 bg-indigo-600 flex items-center justify-center w-10 h-10" - ), - a - ) - ) - ), - div( - cls("flex-1 min-w-0"), - p( - cls("text-sm font-medium text-gray-900"), - title, - span(cls("float-right"), right) - ), - subtitle.map(st => - p( - cls("text-sm text-gray-500 truncate"), - st - ) - ) - ) - ) - ) - - def unordered( - children: Modifier[HtmlElement] - ): ReactiveHtmlElement[org.scalajs.dom.html.UList] = - ul( - cls("relative z-0 divide-y divide-gray-200"), - role("list"), - children - ) - - def listSection( - header: String, - list: HtmlElement - ): Div = - div( - cls("relative"), - div( - cls( - "z-10 sticky top-0 border-t border-b border-gray-200 bg-gray-50 px-6 py-1 text-sm font-medium text-gray-500" - ), - header - ), - list - ) - - def navigation(sections: Modifier[HtmlElement]): HtmlElement = - navTag( - cls("flex-1 min-h-0 overflow-y-auto"), - sections - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala deleted file mode 100644 index 6027404..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/ModalComponentsModule.scala +++ /dev/null @@ -1,54 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait ModalComponentsModule: - object modal: - def modalDialog( - content: HtmlElement, - closeMod: Modifier[HtmlElement] - ): HtmlElement = - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")), - closeMod - ) - ) - - div( - cls("fixed inset-0 z-20 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-7xl sm:align-middle" - ), - content - ) - ) - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/PageComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/PageComponentsModule.scala deleted file mode 100644 index d5a422e..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/PageComponentsModule.scala +++ /dev/null @@ -1,49 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext - -trait PageComponentsModule: - - object page: - def container( - children: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls("max-w-7xl mx-auto h-full px-4 sm:px-6 lg:px-8 overflow-y-auto"), - children - ) - - def singleColumn( - header: Modifier[HtmlElement] - )(children: Modifier[HtmlElement]*): HtmlElement = - div( - cls("p-8 bg-gray-100 h-full"), - header, - children - ) - - def pageHeader( - title: Modifier[HtmlElement], - right: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] = None - ): HtmlElement = - div( - cls("pb-5 border-b border-gray-200"), - div(cls("float-right"), right), - h1( - cls("text-2xl leading-6 font-medium text-gray-900"), - title - ), - subtitle.map( - p( - cls("text-sm font-medium text-gray-500"), - _ - ) - ) - ) - - def clickable: Modifier[HtmlElement] = - cls("text-sm font-medium text-indigo-600 hover:text-indigo-400") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/TableComponentsModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/TableComponentsModule.scala deleted file mode 100644 index 417ea56..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/TableComponentsModule.scala +++ /dev/null @@ -1,86 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L -import com.raquo.laminar.api.L.{*, given} - -trait TableComponentsModule: - - object tables: - - def tableSection( - title: Modifier[HtmlElement], - subtitle: Option[Modifier[HtmlElement]] = None, - actions: Modifier[HtmlElement]* - )( - table: Modifier[HtmlElement]* - ): HtmlElement = - div( - cls("px-4 sm:px-6 lg:px-8"), - div( - cls("sm:flex sm:items-center"), - div( - cls("sm:flex-auto"), - h1(cls("text-base font-semibold leading-6 text-gray-900"), title), - subtitle.map(st => p(cls("mt-2 text-sm text-gray-700"), st)) - ), - div(cls("mt-4 sm:ml-16 sm:mt-0 sm:flex-none"), actions) - ), - div( - cls("mt-8 flow-root"), - div( - cls("-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"), - div( - cls("inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"), - table - ) - ) - ) - ) - - def simpleTable(header: Modifier[HtmlElement]*)( - body: Modifier[HtmlElement]* - ): HtmlElement = - table( - cls("min-w-full divide-y divide-gray-300"), - thead(header), - tbody(cls("divide-y divide-gray-200"), body) - ) - - def headerRow( - mods: Modifier[HtmlElement]* - )(cells: HtmlElement*): HtmlElement = - tr( - cells.zipWithIndex.map((c, i) => - if i == 0 then c.amend(cls("py-3.5 pl-4 pr-3 sm:pl-0")) - else if i == cells.length - 1 then - c.amend(cls("py-3.5 pr-4 pl-3 sm:pr-0")) - else c.amend(cls("py-3.5 px-3")) - ) - ) - - def dataRow( - mods: Modifier[HtmlElement]* - )(cells: HtmlElement*): HtmlElement = - tr( - mods, - cells.zipWithIndex.map((c, i) => - if i == 0 then c.amend(cls("py-4 pl-4 pr-3 sm:pl-0")) - else if i == cells.length - 1 then - c.amend(cls("py-4 pr-4 pl-3 sm:pr-0")) - else c.amend(cls("py-4 px-3")) - ) - ) - - def headerCell(content: Modifier[HtmlElement]): HtmlElement = - th( - cls("text-left text-sm font-semibold text-gray-900"), - content - ) - - def dataCell(content: Modifier[HtmlElement]): HtmlElement = - td( - cls("whitespace-nowrap text-sm text-gray-500"), - content - ) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/TailwindUICatalogue.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/TailwindUICatalogue.scala deleted file mode 100644 index d97fd48..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tailwind/ui/TailwindUICatalogue.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.laminar -package tailwind -package ui - -import com.raquo.laminar.api.L.{*, given} - -trait TailwindUICatalogueModule - extends BadgeComponentsModule - with ButtonComponentsModule - with HeadingsComponentsModule - with IconsModule - with FormComponentsModule - with LayoutModule - with ListComponentsModule - with ModalComponentsModule - with PageComponentsModule - with TableComponentsModule - with DetailComponentsModule