diff --git a/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 3412f3f..a2f8519 100644 --- a/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -152,7 +152,7 @@ ) ) - inline def x(extraClasses: String): SvgElement = + inline def x(extraClasses: String, sw: String = "2"): SvgElement = svg( cls(extraClasses), xmlns("http://www.w3.org/2000/svg"), @@ -163,7 +163,7 @@ path( strokeLineCap("round"), strokeLineJoin("round"), - strokeWidth("2"), + strokeWidth(sw), d("M6 18L18 6M6 6l12 12") ) ) diff --git a/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 3412f3f..a2f8519 100644 --- a/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -152,7 +152,7 @@ ) ) - inline def x(extraClasses: String): SvgElement = + inline def x(extraClasses: String, sw: String = "2"): SvgElement = svg( cls(extraClasses), xmlns("http://www.w3.org/2000/svg"), @@ -163,7 +163,7 @@ path( strokeLineCap("round"), strokeLineJoin("round"), - strokeWidth("2"), + strokeWidth(sw), d("M6 18L18 6M6 6l12 12") ) ) diff --git a/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Modal.scala b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Modal.scala new file mode 100644 index 0000000..266060a --- /dev/null +++ b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Modal.scala @@ -0,0 +1,47 @@ +package works.iterative.ui.components.tailwind + +import com.raquo.laminar.api.L.{*, given} + +object Modal: + def render(elem: HtmlElement, close: 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")), + close + ) + ) + + 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" + ), + elem + ) + ) + ) diff --git a/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 3412f3f..a2f8519 100644 --- a/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -152,7 +152,7 @@ ) ) - inline def x(extraClasses: String): SvgElement = + inline def x(extraClasses: String, sw: String = "2"): SvgElement = svg( cls(extraClasses), xmlns("http://www.w3.org/2000/svg"), @@ -163,7 +163,7 @@ path( strokeLineCap("round"), strokeLineJoin("round"), - strokeWidth("2"), + strokeWidth(sw), d("M6 18L18 6M6 6l12 12") ) ) diff --git a/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Modal.scala b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Modal.scala new file mode 100644 index 0000000..266060a --- /dev/null +++ b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/Modal.scala @@ -0,0 +1,47 @@ +package works.iterative.ui.components.tailwind + +import com.raquo.laminar.api.L.{*, given} + +object Modal: + def render(elem: HtmlElement, close: 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")), + close + ) + ) + + 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" + ), + elem + ) + ) + ) diff --git a/ui/components/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index ea6891f..3b1128d 100644 --- a/ui/components/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/components/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -10,6 +10,7 @@ import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.tailwind.Icons type ValueContent = String | Node type OptionalValueContent = ValueContent | Option[ValueContent] @@ -40,7 +41,8 @@ title: String, subtitle: String, data: List[LabeledValue], - actions: Option[Modifier[HtmlElement]] + actions: Option[Modifier[HtmlElement]] = None, + close: Option[Modifier[HtmlElement]] = None ): private def renderDataRow(value: LabeledValue): Option[HtmlElement] = @@ -57,7 +59,20 @@ def element: HtmlElement = div( - cls := "bg-white shadow overflow-hidden sm:rounded-lg", + cls := "relative bg-white shadow overflow-hidden sm:rounded-lg", + close.map(mods => + div( + cls("absolute top-0 right-0 hidden pt-4 pr-4 sm:block"), + button( + cls( + "rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" + ), + mods, + span(cls("sr-only"), "Close"), + Icons.outline.x("h-6 w-6", "1.5") + ) + ) + ), div( cls := "px-4 py-5 sm:px-6", h3(cls := "text-lg leading-6 font-medium text-gray-900", title),