diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 5a5a1e0..8f2271e 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -206,5 +206,19 @@ ) ) + inline def paperclip(size: Int = defaultSize) = + svg( + cls := s"h-${size} w-${size} 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 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", + clipRule := "evenodd" + ) + ) + end solid end Icons diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 5a5a1e0..8f2271e 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -206,5 +206,19 @@ ) ) + inline def paperclip(size: Int = defaultSize) = + svg( + cls := s"h-${size} w-${size} 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 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", + clipRule := "evenodd" + ) + ) + end solid end Icons diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala new file mode 100644 index 0000000..08341c9 --- /dev/null +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala @@ -0,0 +1,54 @@ +package cz.e_bs.cmi.mdr.pdb.app.components.files + +import com.raquo.laminar.api.L.{*, given} +import cz.e_bs.cmi.mdr.pdb.app.components.Icons + +case class File(name: String, url: String) + +object File: + extension (m: File) + def toHtml: HtmlElement = + li( + cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), + div( + cls("w-0 flex-1 flex items-center"), + Icons.solid + .paperclip() + .amend(svg.cls := "flex-shrink-0 text-gray-400"), + span(cls("ml-2 flex-1 w-0 truncate"), m.name) + ), + div( + cls("ml-4 flex-shrink-0 flex space-x-4"), + a( + href(m.url), + cls("font-medium text-indigo-600 hover:text-indigo-500"), + "Otevřít" + ), + span(cls("text-gray-300"), "|"), + a( + href("#"), + cls("font-medium text-indigo-600 hover:text-indigo-500"), + "Odebrat" + ) + ) + ) + +case class FileList( + files: Signal[List[File]] +) + +object FileList: + extension (m: FileList) + def toHtml: HtmlElement = + div( + ul( + role("list"), + cls("border border-gray-200 rounded-md divide-y divide-gray-200"), + children <-- m.files.map(_.map(_.toHtml)) + ), + button( + tpe := "button", + cls := "mt-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", + "Přidat soubor" + ) + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 5a5a1e0..8f2271e 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -206,5 +206,19 @@ ) ) + inline def paperclip(size: Int = defaultSize) = + svg( + cls := s"h-${size} w-${size} 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 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", + clipRule := "evenodd" + ) + ) + end solid end Icons diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala new file mode 100644 index 0000000..08341c9 --- /dev/null +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala @@ -0,0 +1,54 @@ +package cz.e_bs.cmi.mdr.pdb.app.components.files + +import com.raquo.laminar.api.L.{*, given} +import cz.e_bs.cmi.mdr.pdb.app.components.Icons + +case class File(name: String, url: String) + +object File: + extension (m: File) + def toHtml: HtmlElement = + li( + cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), + div( + cls("w-0 flex-1 flex items-center"), + Icons.solid + .paperclip() + .amend(svg.cls := "flex-shrink-0 text-gray-400"), + span(cls("ml-2 flex-1 w-0 truncate"), m.name) + ), + div( + cls("ml-4 flex-shrink-0 flex space-x-4"), + a( + href(m.url), + cls("font-medium text-indigo-600 hover:text-indigo-500"), + "Otevřít" + ), + span(cls("text-gray-300"), "|"), + a( + href("#"), + cls("font-medium text-indigo-600 hover:text-indigo-500"), + "Odebrat" + ) + ) + ) + +case class FileList( + files: Signal[List[File]] +) + +object FileList: + extension (m: FileList) + def toHtml: HtmlElement = + div( + ul( + role("list"), + cls("border border-gray-200 rounded-md divide-y divide-gray-200"), + children <-- m.files.map(_.map(_.toHtml)) + ), + button( + tpe := "button", + cls := "mt-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", + "Přidat soubor" + ) + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala index 4cdb478..865ffbc 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala @@ -4,79 +4,89 @@ import io.laminext.syntax.core.* import cz.e_bs.cmi.mdr.pdb.app.components.CustomAttrs +case class ComboBox( + id: String, + options: Signal[List[ComboBox.Option]], + valueUpdates: Observer[List[String]] +) + object ComboBox: - object ComboOption: - case class ViewModel(value: String, active: Boolean) - def apply(m: ViewModel): HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value + + extension (m: ComboBox) + def toHtml: HtmlElement = + val isOpen = Var(false) + div( + cls := "relative mt-1", + input( + idAttr := m.id, + tpe := "text", + cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", + role := "combobox", + aria.controls := "options", + aria.expanded := false, + onClick.mapTo(true) --> isOpen.writer ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) + button( + tpe := "button", + cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { + import svg.* + svg( + cls := "h-5 w-5 text-gray-400", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + CustomAttrs.svg.ariaHidden := true, + path( + fillRule := "evenodd", + d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", + clipRule := "evenodd" ) - } - ) - else emptyNode - ) - case class ViewModel(id: String, options: List[ComboOption.ViewModel]) - def apply(m: ViewModel): HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - m.options.map(ComboOption(_)) + } + ), + ul( + cls <-- isOpen.signal.switch("", "hidden"), + cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", + idAttr := "options", + role := "listbox", + children <-- m.options.map(_.map(_.toHtml)) + ) ) - ) + + case class Option(value: String, active: Boolean) + + object Option: + extension (m: Option) + def toHtml: HtmlElement = + li( + cls := "relative cursor-default select-none py-2 pl-8 pr-4", + cls := (if m.active then "text-white bg-indigo-600" + else "text-gray-900"), + idAttr := "option-0", + role := "option", + tabIndex := -1, + span( + cls := "block truncate", + m.value + ), + if m.active then + span( + cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", + cls := (if m.active then "text-white" else "text-indigo-600"), { + import svg.* + svg( + cls := "h-5 w-5", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + CustomAttrs.svg.ariaHidden := true, + path( + fillRule := "evenodd", + d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", + clipRule := "evenodd" + ) + ) + } + ) + else emptyNode + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 5a5a1e0..8f2271e 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -206,5 +206,19 @@ ) ) + inline def paperclip(size: Int = defaultSize) = + svg( + cls := s"h-${size} w-${size} 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 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", + clipRule := "evenodd" + ) + ) + end solid end Icons diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala new file mode 100644 index 0000000..08341c9 --- /dev/null +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala @@ -0,0 +1,54 @@ +package cz.e_bs.cmi.mdr.pdb.app.components.files + +import com.raquo.laminar.api.L.{*, given} +import cz.e_bs.cmi.mdr.pdb.app.components.Icons + +case class File(name: String, url: String) + +object File: + extension (m: File) + def toHtml: HtmlElement = + li( + cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), + div( + cls("w-0 flex-1 flex items-center"), + Icons.solid + .paperclip() + .amend(svg.cls := "flex-shrink-0 text-gray-400"), + span(cls("ml-2 flex-1 w-0 truncate"), m.name) + ), + div( + cls("ml-4 flex-shrink-0 flex space-x-4"), + a( + href(m.url), + cls("font-medium text-indigo-600 hover:text-indigo-500"), + "Otevřít" + ), + span(cls("text-gray-300"), "|"), + a( + href("#"), + cls("font-medium text-indigo-600 hover:text-indigo-500"), + "Odebrat" + ) + ) + ) + +case class FileList( + files: Signal[List[File]] +) + +object FileList: + extension (m: FileList) + def toHtml: HtmlElement = + div( + ul( + role("list"), + cls("border border-gray-200 rounded-md divide-y divide-gray-200"), + children <-- m.files.map(_.map(_.toHtml)) + ), + button( + tpe := "button", + cls := "mt-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", + "Přidat soubor" + ) + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala index 4cdb478..865ffbc 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala @@ -4,79 +4,89 @@ import io.laminext.syntax.core.* import cz.e_bs.cmi.mdr.pdb.app.components.CustomAttrs +case class ComboBox( + id: String, + options: Signal[List[ComboBox.Option]], + valueUpdates: Observer[List[String]] +) + object ComboBox: - object ComboOption: - case class ViewModel(value: String, active: Boolean) - def apply(m: ViewModel): HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value + + extension (m: ComboBox) + def toHtml: HtmlElement = + val isOpen = Var(false) + div( + cls := "relative mt-1", + input( + idAttr := m.id, + tpe := "text", + cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", + role := "combobox", + aria.controls := "options", + aria.expanded := false, + onClick.mapTo(true) --> isOpen.writer ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) + button( + tpe := "button", + cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { + import svg.* + svg( + cls := "h-5 w-5 text-gray-400", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + CustomAttrs.svg.ariaHidden := true, + path( + fillRule := "evenodd", + d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", + clipRule := "evenodd" ) - } - ) - else emptyNode - ) - case class ViewModel(id: String, options: List[ComboOption.ViewModel]) - def apply(m: ViewModel): HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - m.options.map(ComboOption(_)) + } + ), + ul( + cls <-- isOpen.signal.switch("", "hidden"), + cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", + idAttr := "options", + role := "listbox", + children <-- m.options.map(_.map(_.toHtml)) + ) ) - ) + + case class Option(value: String, active: Boolean) + + object Option: + extension (m: Option) + def toHtml: HtmlElement = + li( + cls := "relative cursor-default select-none py-2 pl-8 pr-4", + cls := (if m.active then "text-white bg-indigo-600" + else "text-gray-900"), + idAttr := "option-0", + role := "option", + tabIndex := -1, + span( + cls := "block truncate", + m.value + ), + if m.active then + span( + cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", + cls := (if m.active then "text-white" else "text-indigo-600"), { + import svg.* + svg( + cls := "h-5 w-5", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + CustomAttrs.svg.ariaHidden := true, + path( + fillRule := "evenodd", + d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", + clipRule := "evenodd" + ) + ) + } + ) + else emptyNode + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/FormRow.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/FormRow.scala index 4c95f39..4007853 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/FormRow.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/FormRow.scala @@ -4,17 +4,19 @@ case class FormRow(id: String, label: String, content: Modifier[Div]) -extension (m: FormRow) - def toHtml: 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( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), +object FormRow: + + extension (m: FormRow) + def toHtml: HtmlElement = div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content + cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", + label( + forId := m.id, + cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", + m.label + ), + div( + cls := "mt-1 sm:mt-0 sm:col-span-2", + m.content + ) ) - ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 5a5a1e0..8f2271e 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -206,5 +206,19 @@ ) ) + inline def paperclip(size: Int = defaultSize) = + svg( + cls := s"h-${size} w-${size} 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 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", + clipRule := "evenodd" + ) + ) + end solid end Icons diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala new file mode 100644 index 0000000..08341c9 --- /dev/null +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/files/FileList.scala @@ -0,0 +1,54 @@ +package cz.e_bs.cmi.mdr.pdb.app.components.files + +import com.raquo.laminar.api.L.{*, given} +import cz.e_bs.cmi.mdr.pdb.app.components.Icons + +case class File(name: String, url: String) + +object File: + extension (m: File) + def toHtml: HtmlElement = + li( + cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), + div( + cls("w-0 flex-1 flex items-center"), + Icons.solid + .paperclip() + .amend(svg.cls := "flex-shrink-0 text-gray-400"), + span(cls("ml-2 flex-1 w-0 truncate"), m.name) + ), + div( + cls("ml-4 flex-shrink-0 flex space-x-4"), + a( + href(m.url), + cls("font-medium text-indigo-600 hover:text-indigo-500"), + "Otevřít" + ), + span(cls("text-gray-300"), "|"), + a( + href("#"), + cls("font-medium text-indigo-600 hover:text-indigo-500"), + "Odebrat" + ) + ) + ) + +case class FileList( + files: Signal[List[File]] +) + +object FileList: + extension (m: FileList) + def toHtml: HtmlElement = + div( + ul( + role("list"), + cls("border border-gray-200 rounded-md divide-y divide-gray-200"), + children <-- m.files.map(_.map(_.toHtml)) + ), + button( + tpe := "button", + cls := "mt-5 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", + "Přidat soubor" + ) + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala index 4cdb478..865ffbc 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/ComboBox.scala @@ -4,79 +4,89 @@ import io.laminext.syntax.core.* import cz.e_bs.cmi.mdr.pdb.app.components.CustomAttrs +case class ComboBox( + id: String, + options: Signal[List[ComboBox.Option]], + valueUpdates: Observer[List[String]] +) + object ComboBox: - object ComboOption: - case class ViewModel(value: String, active: Boolean) - def apply(m: ViewModel): HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value + + extension (m: ComboBox) + def toHtml: HtmlElement = + val isOpen = Var(false) + div( + cls := "relative mt-1", + input( + idAttr := m.id, + tpe := "text", + cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", + role := "combobox", + aria.controls := "options", + aria.expanded := false, + onClick.mapTo(true) --> isOpen.writer ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) + button( + tpe := "button", + cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { + import svg.* + svg( + cls := "h-5 w-5 text-gray-400", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + CustomAttrs.svg.ariaHidden := true, + path( + fillRule := "evenodd", + d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", + clipRule := "evenodd" ) - } - ) - else emptyNode - ) - case class ViewModel(id: String, options: List[ComboOption.ViewModel]) - def apply(m: ViewModel): HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - m.options.map(ComboOption(_)) + } + ), + ul( + cls <-- isOpen.signal.switch("", "hidden"), + cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", + idAttr := "options", + role := "listbox", + children <-- m.options.map(_.map(_.toHtml)) + ) ) - ) + + case class Option(value: String, active: Boolean) + + object Option: + extension (m: Option) + def toHtml: HtmlElement = + li( + cls := "relative cursor-default select-none py-2 pl-8 pr-4", + cls := (if m.active then "text-white bg-indigo-600" + else "text-gray-900"), + idAttr := "option-0", + role := "option", + tabIndex := -1, + span( + cls := "block truncate", + m.value + ), + if m.active then + span( + cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", + cls := (if m.active then "text-white" else "text-indigo-600"), { + import svg.* + svg( + cls := "h-5 w-5", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + CustomAttrs.svg.ariaHidden := true, + path( + fillRule := "evenodd", + d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", + clipRule := "evenodd" + ) + ) + } + ) + else emptyNode + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/FormRow.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/FormRow.scala index 4c95f39..4007853 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/FormRow.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/form/FormRow.scala @@ -4,17 +4,19 @@ case class FormRow(id: String, label: String, content: Modifier[Div]) -extension (m: FormRow) - def toHtml: 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( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), +object FormRow: + + extension (m: FormRow) + def toHtml: HtmlElement = div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content + cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", + label( + forId := m.id, + cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", + m.label + ), + div( + cls := "mt-1 sm:mt-0 sm:col-span-2", + m.content + ) ) - ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala index 61428d9..dd47abc 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/detail/components/UpravDukazForm.scala @@ -6,6 +6,7 @@ import cz.e_bs.cmi.mdr.pdb.app.components.form.* import org.scalajs.dom import com.raquo.laminar.nodes.ReactiveHtmlElement +import cz.e_bs.cmi.mdr.pdb.app.components.files object UpravDukazForm: object SubmitButtons: @@ -43,17 +44,19 @@ FormRow( "dokumenty", "Dokumenty", - ComboBox( - ComboBox.ViewModel( - "dokumenty", - List( - ComboBox.ComboOption.ViewModel( - "Nebyly nalezeny žádné dokumenty.", - false + files + .FileList( + Val( + List( + files.File( + "Pracovní smlouva", + "http://example.com/123.doc" + ) ) ) ) - ).amend(cls := "max-w-lg") + .toHtml + .amend(idAttr := "dokumenty", cls("max-w-lg")) ).toHtml, FormRow( "komentar", @@ -68,7 +71,7 @@ ), p( cls := "mt-2 text-sm text-gray-500", - "Doplňte prosímpotřebné informace související s doložením kritéria, včetně případných limitací." + "Doplňte prosím potřebné informace související s doložením kritéria, včetně případných limitací." ) ) ).toHtml