{-| Module: IHP.View.CSSFramework Description: Adds support for bootstrap, tailwind, etc. to IHP Copyright: (c) digitally induced GmbH, 2020 -} module IHP.View.CSSFramework where import IHP.Prelude import IHP.FlashMessages.Types import qualified Text.Blaze.Html5 as Blaze import IHP.HSX.QQ (hsx) import IHP.HSX.ToHtml () import IHP.View.Types import IHP.View.Classes import IHP.ModelSupport import IHP.Breadcrumb.Types import IHP.Pagination.Helpers import IHP.Pagination.Types -- | Provides an unstyled CSSFramework -- -- This way we can later add more properties to the CSSFramework without having to update all the CSS Frameworks manually instance Default CSSFramework where def = CSSFramework { styledFlashMessage = \cssFramework -> \case SuccessFlashMessage message -> [hsx|
{message}
|] ErrorFlashMessage message -> [hsx|
{message}
|] , styledFlashMessages , styledFormField , styledTextFormField , styledTextareaFormField , styledCheckboxFormField , styledSelectFormField , styledRadioFormField , styledFormGroup , styledSubmitButton , styledSubmitButtonClass , styledFormFieldHelp , styledInputClass , styledInputInvalidClass , styledFormGroupClass , styledValidationResult , styledValidationResultClass , styledPagination , styledPaginationPageLink , styledPaginationDotDot , styledPaginationItemsPerPageSelector , styledPaginationLinkPrevious , styledPaginationLinkNext , styledBreadcrumb , styledBreadcrumbItem } where styledFlashMessages cssFramework flashMessages = forEach flashMessages (cssFramework.styledFlashMessage cssFramework) styledFormField :: CSSFramework -> FormField -> Blaze.Html styledFormField cssFramework@CSSFramework {styledValidationResult, styledTextFormField, styledCheckboxFormField, styledSelectFormField, styledRadioFormField, styledTextareaFormField} formField = formGroup renderInner where renderInner = case formField.fieldType of TextInput -> styledTextFormField cssFramework "text" formField validationResult NumberInput -> styledTextFormField cssFramework "number" formField validationResult UrlInput -> styledTextFormField cssFramework "url" formField validationResult PasswordInput -> styledTextFormField cssFramework "password" formField validationResult ColorInput -> styledTextFormField cssFramework "color" formField validationResult EmailInput -> styledTextFormField cssFramework "email" formField validationResult DateInput -> styledTextFormField cssFramework "date" formField validationResult DateTimeInput -> styledTextFormField cssFramework "datetime-local" formField validationResult CheckboxInput -> styledCheckboxFormField cssFramework formField validationResult HiddenInput -> styledTextFormField cssFramework "hidden" formField validationResult TextareaInput -> styledTextareaFormField cssFramework formField validationResult SelectInput {} -> styledSelectFormField cssFramework formField validationResult RadioInput {} -> styledRadioFormField cssFramework formField validationResult FileInput -> styledTextFormField cssFramework "file" formField validationResult validationResult :: Blaze.Html validationResult = unless formField.disableValidationResult (styledValidationResult cssFramework formField) -- | Wraps the input inside a @
...
@ (unless @disableGroup = True@) formGroup :: Blaze.Html -> Blaze.Html formGroup renderInner = case formField of FormField { disableGroup = True } -> renderInner FormField { fieldInputId } -> styledFormGroup cssFramework fieldInputId renderInner styledFormGroup :: CSSFramework -> Text -> Blaze.Html -> Blaze.Html styledFormGroup cssFramework@CSSFramework {styledFormGroupClass} fieldInputId renderInner = [hsx|
fieldInputId}>{renderInner}
|] styledCheckboxFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledCheckboxFormField cssFramework@CSSFramework {styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, required, autofocus } validationResult = do [hsx|
{element}
|] where inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField -- If the checkbox is checked off, the browser will not send the parameter as part of the form. -- This will then make it impossible to set a field to False using a checkbox. -- For that we add the "hidden" input type. theInput = [hsx| |] element = if disableLabel then [hsx|
{theInput} {validationResult} {helpText}
|] else [hsx| {theInput} {validationResult} {helpText} |] styledTextFormField :: CSSFramework -> Text -> FormField -> Blaze.Html -> Blaze.Html styledTextFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} inputType formField@FormField {fieldType, fieldName, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, placeholder, required, autofocus } validationResult = [hsx| {label} {validationResult} {helpText} |] where label = unless (disableLabel || null fieldLabel) [hsx||] inputClass = (styledInputClass cssFramework formField, True) inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField -- If there's no value, then we want to hide the "value" attribute. maybeValue = if fieldValue == "" then Nothing else Just fieldValue styledSelectFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledSelectFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, placeholder, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, required, autofocus } validationResult = [hsx| {label} {validationResult} {helpText} |] where label = unless disableLabel [hsx||] inputClass = (styledInputClass cssFramework formField, True) inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField isValueSelected = any (\(_, optionValue) -> optionValue == fieldValue) (options fieldType) -- Get a single option. getOption (optionLabel, optionValue) = [hsx| |] styledRadioFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledRadioFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, placeholder, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, required, autofocus } validationResult = [hsx| {label}
{forEach (options fieldType) (getRadio)}
{validationResult} {helpText} |] where label = unless disableLabel [hsx||] inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField -- Get a single radio button. getRadio (optionLabel, optionValue) = [hsx|
{label}
|] where optionId = fieldInputId <> "_" <> optionValue label = unless disableLabel [hsx||] styledTextareaFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledTextareaFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, placeholder, required, autofocus } validationResult = [hsx| {label} {validationResult}{helpText}|] where label = unless (disableLabel || null fieldLabel) [hsx||] inputClass = (styledInputClass cssFramework formField, True) inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField styledValidationResult :: CSSFramework -> FormField -> Blaze.Html styledValidationResult cssFramework formField@FormField { validatorResult = Just violation } = let className :: Text = cssFramework.styledValidationResultClass message = case violation of TextViolation text -> [hsx|{text}|] HtmlViolation html -> Blaze.preEscapedToHtml html in [hsx|
{message}
|] styledValidationResult _ _ = mempty styledValidationResultClass = "" styledSubmitButton cssFramework SubmitButton { label, buttonClass, buttonDisabled } = let className :: Text = cssFramework.styledSubmitButtonClass in [hsx||] styledInputClass _ _ = "" styledInputInvalidClass _ _ = "invalid" styledFormGroupClass = "" styledFormFieldHelp _ FormField { helpText = "" } = mempty styledFormFieldHelp _ FormField { helpText } = [hsx|

{helpText}

|] styledSubmitButtonClass = "" styledPagination :: CSSFramework -> PaginationView -> Blaze.Html styledPagination _ paginationView = [hsx|
|] styledPaginationPageLink :: CSSFramework -> Pagination -> ByteString -> Int -> Blaze.Html styledPaginationPageLink _ pagination@Pagination {currentPage} pageUrl pageNumber = let linkClass = classes ["page-item", ("active", pageNumber == currentPage)] in [hsx|
  • {show pageNumber}
  • |] styledPaginationDotDot :: CSSFramework -> Pagination -> Blaze.Html styledPaginationDotDot _ _ = [hsx|
  • |] styledPaginationItemsPerPageSelector :: CSSFramework -> Pagination -> (Int -> ByteString) -> Blaze.Html styledPaginationItemsPerPageSelector _ pagination@Pagination {pageSize} itemsPerPageUrl = let oneOption :: Int -> Blaze.Html oneOption n = [hsx||] in [hsx|{forEach [10,20,50,100,200] oneOption}|] styledPaginationLinkPrevious :: CSSFramework -> Pagination -> ByteString -> Blaze.Html styledPaginationLinkPrevious _ pagination@Pagination {currentPage} pageUrl = let prevClass = classes ["page-item", ("disabled", not $ hasPreviousPage pagination)] url = if hasPreviousPage pagination then pageUrl else "#" in [hsx|
  • Previous
  • |] styledPaginationLinkNext :: CSSFramework -> Pagination -> ByteString -> Blaze.Html styledPaginationLinkNext _ pagination@Pagination {currentPage} pageUrl = let nextClass = classes ["page-item", ("disabled", not $ hasNextPage pagination)] url = if hasNextPage pagination then pageUrl else "#" in [hsx|
  • Next
  • |] styledBreadcrumb :: CSSFramework -> [BreadcrumbItem]-> BreadcrumbsView -> Blaze.Html styledBreadcrumb _ _ breadcrumbsView = [hsx| |] styledBreadcrumbItem :: CSSFramework -> [ BreadcrumbItem ]-> BreadcrumbItem -> Bool -> Blaze.Html styledBreadcrumbItem _ breadcrumbItems breadcrumbItem@BreadcrumbItem {breadcrumbLabel, url} isLast = let breadcrumbsClasses = classes ["breadcrumb-item", ("active", isLast)] in case url of Nothing -> [hsx|
  • {breadcrumbLabel}
  • |] Just url -> [hsx|
  • {breadcrumbLabel}
  • |] bootstrap :: CSSFramework bootstrap = def { styledFlashMessage , styledSubmitButtonClass , styledFormGroupClass , styledFormFieldHelp , styledInputClass , styledInputInvalidClass , styledValidationResultClass } where styledFlashMessage _ (SuccessFlashMessage message) = [hsx|
    {message}
    |] styledFlashMessage _ (ErrorFlashMessage message) = [hsx|
    {message}
    |] styledInputClass _ FormField { fieldType = FileInput } = "form-control-file" styledInputClass _ FormField {} = "form-control" styledInputInvalidClass _ _ = "is-invalid" styledFormFieldHelp _ FormField { helpText = "" } = mempty styledFormFieldHelp _ FormField { helpText } = [hsx|{helpText}|] styledFormGroupClass = "mb-3" styledValidationResultClass = "invalid-feedback" styledSubmitButtonClass = "btn btn-primary" bootstrap4 :: CSSFramework bootstrap4 = def { styledFlashMessage , styledFormField , styledTextFormField , styledTextareaFormField , styledCheckboxFormField , styledSelectFormField , styledFormGroup , styledSubmitButton , styledSubmitButtonClass , styledFormFieldHelp , styledInputClass , styledInputInvalidClass , styledFormGroupClass , styledValidationResult , styledValidationResultClass , styledPagination , styledPaginationPageLink , styledPaginationDotDot , styledPaginationItemsPerPageSelector , styledPaginationLinkPrevious , styledPaginationLinkNext , styledBreadcrumb , styledBreadcrumbItem } where styledFlashMessage _ (SuccessFlashMessage message) = [hsx|
    {message}
    |] styledFlashMessage _ (ErrorFlashMessage message) = [hsx|
    {message}
    |] styledInputClass _ FormField { fieldType = FileInput } = "form-control-file" styledInputClass _ FormField {} = "form-control" styledInputInvalidClass _ _ = "is-invalid" styledFormFieldHelp _ FormField { helpText = "" } = mempty styledFormFieldHelp _ FormField { helpText } = [hsx|{helpText}|] styledFormGroupClass = "form-group" styledValidationResultClass = "invalid-feedback" styledSubmitButtonClass = "btn btn-primary" styledFormField :: CSSFramework -> FormField -> Blaze.Html styledFormField cssFramework@CSSFramework {styledValidationResult, styledTextFormField, styledCheckboxFormField, styledSelectFormField, styledRadioFormField, styledTextareaFormField} formField = formGroup renderInner where renderInner = case formField.fieldType of TextInput -> styledTextFormField cssFramework "text" formField validationResult NumberInput -> styledTextFormField cssFramework "number" formField validationResult PasswordInput -> styledTextFormField cssFramework "password" formField validationResult ColorInput -> styledTextFormField cssFramework "color" formField validationResult EmailInput -> styledTextFormField cssFramework "email" formField validationResult DateInput -> styledTextFormField cssFramework "date" formField validationResult DateTimeInput -> styledTextFormField cssFramework "datetime-local" formField validationResult CheckboxInput -> styledCheckboxFormField cssFramework formField validationResult HiddenInput -> styledTextFormField cssFramework "hidden" formField { disableLabel = True, disableGroup = True, disableValidationResult = True } validationResult TextareaInput -> styledTextareaFormField cssFramework formField validationResult SelectInput {} -> styledSelectFormField cssFramework formField validationResult RadioInput {} -> styledRadioFormField cssFramework formField validationResult FileInput -> styledTextFormField cssFramework "file" formField validationResult validationResult :: Blaze.Html validationResult = unless formField.disableValidationResult (styledValidationResult cssFramework formField) -- | Wraps the input inside a @
    ...
    @ (unless @disableGroup = True@) formGroup :: Blaze.Html -> Blaze.Html formGroup renderInner = case formField of FormField { disableGroup = True } -> renderInner FormField { fieldInputId } -> styledFormGroup cssFramework fieldInputId renderInner styledFormGroup :: CSSFramework -> Text -> Blaze.Html -> Blaze.Html styledFormGroup cssFramework@CSSFramework {styledFormGroupClass} fieldInputId renderInner = [hsx|
    fieldInputId}>{renderInner}
    |] styledCheckboxFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledCheckboxFormField cssFramework@CSSFramework {styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, required, autofocus } validationResult = do [hsx|
    {element}
    |] where inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField -- If the checkbox is checked off, the browser will not send the parameter as part of the form. -- This will then make it impossible to set a field to False using a checkbox. -- For that we add the "hidden" input type. theInput = [hsx| |] element = if disableLabel then [hsx|
    {theInput} {validationResult} {helpText}
    |] else [hsx| {theInput} {validationResult} {helpText} |] styledTextFormField :: CSSFramework -> Text -> FormField -> Blaze.Html -> Blaze.Html styledTextFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} inputType formField@FormField {fieldType, fieldName, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, placeholder, required, autofocus } validationResult = [hsx| {label} {validationResult} {helpText} |] where label = unless (disableLabel || null fieldLabel) [hsx||] inputClass = (styledInputClass cssFramework formField, True) inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField -- If there's no value, then we want to hide the "value" attribute. maybeValue = if fieldValue == "" then Nothing else Just fieldValue styledSelectFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledSelectFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, placeholder, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, required, autofocus } validationResult = [hsx| {label} {validationResult} {helpText} |] where label = unless disableLabel [hsx||] inputClass = (styledInputClass cssFramework formField, True) inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField isValueSelected = any (\(_, optionValue) -> optionValue == fieldValue) (options fieldType) -- Get a single option. getOption (optionLabel, optionValue) = [hsx| |] styledTextareaFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledTextareaFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, placeholder, required, autofocus } validationResult = [hsx| {label} {validationResult}{helpText}|] where label = unless (disableLabel || null fieldLabel) [hsx||] inputClass = (styledInputClass cssFramework formField, True) inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField styledValidationResult :: CSSFramework -> FormField -> Blaze.Html styledValidationResult cssFramework formField@FormField { validatorResult = Just violation } = let className :: Text = cssFramework.styledValidationResultClass message = case violation of TextViolation text -> [hsx|{text}|] HtmlViolation html -> Blaze.preEscapedToHtml html in [hsx|
    {message}
    |] styledValidationResult _ _ = mempty styledSubmitButton cssFramework SubmitButton { label, buttonClass, buttonDisabled } = let className :: Text = cssFramework.styledSubmitButtonClass in [hsx||] styledPagination :: CSSFramework -> PaginationView -> Blaze.Html styledPagination _ paginationView = [hsx|
    |] styledPaginationPageLink :: CSSFramework -> Pagination -> ByteString -> Int -> Blaze.Html styledPaginationPageLink _ pagination@Pagination {currentPage} pageUrl pageNumber = let linkClass = classes ["page-item", ("active", pageNumber == currentPage)] in [hsx|
  • {show pageNumber}
  • |] styledPaginationDotDot :: CSSFramework -> Pagination -> Blaze.Html styledPaginationDotDot _ _ = [hsx|
  • |] styledPaginationItemsPerPageSelector :: CSSFramework -> Pagination -> (Int -> ByteString) -> Blaze.Html styledPaginationItemsPerPageSelector _ pagination@Pagination {pageSize} itemsPerPageUrl = let oneOption :: Int -> Blaze.Html oneOption n = [hsx||] in [hsx|{forEach [10,20,50,100,200] oneOption}|] styledPaginationLinkPrevious :: CSSFramework -> Pagination -> ByteString -> Blaze.Html styledPaginationLinkPrevious _ pagination@Pagination {currentPage} pageUrl = let prevClass = classes ["page-item", ("disabled", not $ hasPreviousPage pagination)] url = if hasPreviousPage pagination then pageUrl else "#" in [hsx|
  • Previous
  • |] styledPaginationLinkNext :: CSSFramework -> Pagination -> ByteString -> Blaze.Html styledPaginationLinkNext _ pagination@Pagination {currentPage} pageUrl = let nextClass = classes ["page-item", ("disabled", not $ hasNextPage pagination)] url = if hasNextPage pagination then pageUrl else "#" in [hsx|
  • Next
  • |] styledBreadcrumb :: CSSFramework -> [BreadcrumbItem]-> BreadcrumbsView -> Blaze.Html styledBreadcrumb _ _ breadcrumbsView = [hsx| |] styledBreadcrumbItem :: CSSFramework -> [ BreadcrumbItem ]-> BreadcrumbItem -> Bool -> Blaze.Html styledBreadcrumbItem _ breadcrumbItems breadcrumbItem@BreadcrumbItem {breadcrumbLabel, url} isLast = let breadcrumbsClasses = classes ["breadcrumb-item", ("active", isLast)] in case url of Nothing -> [hsx|
  • {breadcrumbLabel}
  • |] Just url -> [hsx|
  • {breadcrumbLabel}
  • |] tailwind :: CSSFramework tailwind = def { styledFlashMessage , styledTextFormField , styledTextareaFormField , styledCheckboxFormField , styledSelectFormField , styledRadioFormField , styledSubmitButtonClass , styledFormGroupClass , styledFormFieldHelp , styledInputClass , styledInputInvalidClass , styledValidationResultClass , styledPagination , styledPaginationLinkPrevious , styledPaginationLinkNext , styledPaginationPageLink , styledPaginationDotDot , styledPaginationItemsPerPageSelector , styledBreadcrumb , styledBreadcrumbItem } where styledFlashMessage _ (SuccessFlashMessage message) = [hsx|
    {message}
    |] styledFlashMessage _ (ErrorFlashMessage message) = [hsx|
    {message}
    |] styledCheckboxFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledCheckboxFormField cssFramework@CSSFramework {styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, required, autofocus } validationResult = do [hsx|
    {element}
    |] where inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField theInput = [hsx|
    |] element = if disableLabel then [hsx|
    {theInput}
    {validationResult} {helpText}
    |] else [hsx|
    {theInput}
    {validationResult} {helpText}
    |] styledTextFormField :: CSSFramework -> Text -> FormField -> Blaze.Html -> Blaze.Html styledTextFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} inputType formField@FormField {fieldType, fieldName, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, placeholder, required, autofocus } validationResult = [hsx| {label} {validationResult} {helpText} |] where twLabelClass = "font-medium text-gray-700" <> " " <> labelClass label = unless (disableLabel || null fieldLabel) [hsx||] inputClass = (styledInputClass cssFramework formField, True) inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField -- If there's no value, then we want to hide the "value" attribute. maybeValue = if fieldValue == "" then Nothing else Just fieldValue styledTextareaFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledTextareaFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, placeholder, required, autofocus } validationResult = [hsx| {label} {validationResult}{helpText} |] where twLabelClass = classes ["font-medium text-gray-700", (labelClass, not (null labelClass))] label = unless (disableLabel || null fieldLabel) [hsx||] inputClass = (styledInputClass cssFramework formField, True) inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField styledSelectFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledSelectFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, placeholder, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, required, autofocus } validationResult = [hsx| {label} {validationResult} {helpText} |] where twLabelClass = "font-medium text-gray-700" <> " " <> labelClass label = unless disableLabel [hsx||] inputClass = (styledInputClass cssFramework formField, True) inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField isValueSelected = any (\(_, optionValue) -> optionValue == fieldValue) (options fieldType) -- Get a single option. getOption (optionLabel, optionValue) = [hsx| |] styledRadioFormField :: CSSFramework -> FormField -> Blaze.Html -> Blaze.Html styledRadioFormField cssFramework@CSSFramework {styledInputClass, styledInputInvalidClass, styledFormFieldHelp} formField@FormField {fieldType, fieldName, placeholder, fieldLabel, fieldValue, fieldInputId, validatorResult, fieldClass, disabled, disableLabel, disableValidationResult, additionalAttributes, labelClass, required, autofocus } validationResult = [hsx| {label}
    {forEach (options fieldType) (getRadio)}
    {validationResult} {helpText} |] where label = unless disableLabel [hsx||] inputClass = (styledInputClass cssFramework formField, True) inputInvalidClass = styledInputInvalidClass cssFramework formField helpText = styledFormFieldHelp cssFramework formField -- Get a single radio button. getRadio (optionLabel, optionValue) = [hsx|
    {label}
    |] where optionId = fieldInputId <> "_" <> optionValue label = unless disableLabel [hsx||] styledInputClass _ FormField {} = "focus:ring-blue-500 focus:border-blue-500 block w-full border-gray-300 rounded-md" styledInputInvalidClass _ _ = "is-invalid" styledSubmitButtonClass = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" styledFormFieldHelp _ FormField { helpText = "" } = mempty styledFormFieldHelp _ FormField { helpText } = [hsx|

    {helpText}

    |] styledFormGroupClass = "flex flex-col my-6 space-y-2" styledValidationResultClass = "text-red-500 text-xs italic" styledPagination :: CSSFramework -> PaginationView -> Blaze.Html styledPagination _ paginationView@PaginationView {pageUrl, pagination} = let currentPage = pagination.currentPage previousPageUrl = if hasPreviousPage pagination then pageUrl $ currentPage - 1 else "#" nextPageUrl = if hasNextPage pagination then pageUrl $ currentPage + 1 else "#" defaultClass = "relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50" previousClass = classes [ defaultClass , ("disabled", not $ hasPreviousPage pagination) ] nextClass = classes [ defaultClass , ("disabled", not $ hasNextPage pagination) ] previousMobileOnly = [hsx| Previous |] nextMobileOnly = [hsx| Next |] in [hsx|
    {previousMobileOnly} {nextMobileOnly}
    |] styledPaginationLinkPrevious :: CSSFramework -> Pagination -> ByteString -> Blaze.Html styledPaginationLinkPrevious _ pagination@Pagination {currentPage} pageUrl = let prevClass = classes [ "relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50" , ("disabled", not $ hasPreviousPage pagination) ] url = if hasPreviousPage pagination then pageUrl else "#" in [hsx| Previous |] styledPaginationLinkNext :: CSSFramework -> Pagination -> ByteString -> Blaze.Html styledPaginationLinkNext _ pagination@Pagination {currentPage} pageUrl = let nextClass = classes [ "relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50" , ("disabled", not $ hasNextPage pagination) ] url = if hasNextPage pagination then pageUrl else "#" in [hsx| Next |] styledPaginationPageLink :: CSSFramework -> Pagination -> ByteString -> Int -> Blaze.Html styledPaginationPageLink _ pagination@Pagination {currentPage} pageUrl pageNumber = let linkClass = classes [ "relative inline-flex items-center px-4 py-2 border text-sm font-medium" -- Current page , ("z-10 bg-blue-50 border-blue-500 text-blue-600", pageNumber == currentPage) -- Not current page , ("bg-white border-gray-300 text-gray-500 hover:bg-gray-50", pageNumber /= currentPage) ] in [hsx| {show pageNumber} |] styledPaginationDotDot :: CSSFramework -> Pagination -> Blaze.Html styledPaginationDotDot _ _ = [hsx| ... |] styledPaginationItemsPerPageSelector :: CSSFramework -> Pagination -> (Int -> ByteString) -> Blaze.Html styledPaginationItemsPerPageSelector _ pagination@Pagination {pageSize} itemsPerPageUrl = let oneOption :: Int -> Blaze.Html oneOption n = [hsx||] in [hsx|{forEach [10,20,50,100,200] oneOption}|] styledBreadcrumb :: CSSFramework -> [BreadcrumbItem]-> BreadcrumbsView -> Blaze.Html styledBreadcrumb _ _ breadcrumbsView = [hsx| |] styledBreadcrumbItem :: CSSFramework -> [ BreadcrumbItem ]-> BreadcrumbItem -> Bool -> Blaze.Html styledBreadcrumbItem _ breadcrumbItems breadcrumbItem@BreadcrumbItem {breadcrumbLabel, url} isLast = let breadcrumbsClasses = classes ["flex flex-row space-x-2 text-gray-600 items-center", ("active", isLast)] -- Show chevron if item isn't the active one (i.e. the last one). chevronRight = unless isLast [hsx| |] in case url of Nothing -> [hsx|
  • {breadcrumbLabel} {chevronRight}
  • |] Just url -> [hsx|
  • {breadcrumbLabel} {chevronRight}
  • |]