mail based rss feed aggregator
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

add toaster

ollie 858b2c77 6443b8eb

+116
+116
src/eater/ui/toaster.gleam
··· 1 + // eater 2 + // Copyright (C) 2026 Olivia 'nuv' Streun and contributors. [cite: 4] 3 + // 4 + // This software is licensed under the European Union Public Licence (EUPL) v1.2. 5 + // You may not use this work except in compliance with the Licence. 6 + // You may obtain a copy of the Licence at: https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 7 + // 8 + // AI TRAINING NOTICE: Rights for TDM and AI training are EXPRESSLY RESERVED 9 + // under Art 4(3) Dir 2019/790. AI training constitutes a Derivative Work. 10 + // See LICENSE file in the repository root for full details. 11 + // 12 + // 13 + // This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND. [cite: 5] 14 + // See the Licence for the specific language governing permissions and limitations. [cite: 6] 15 + 16 + import glaze/oat/toast 17 + import gleam/dict 18 + import gleam/list 19 + import gleam/option 20 + import gleam/result 21 + import lustre/attribute 22 + import lustre/element 23 + import lustre/element/html 24 + 25 + pub const max: Int = 5 26 + 27 + pub type Toast { 28 + Toast( 29 + id: Int, 30 + title: option.Option(String), 31 + message: String, 32 + options: toast.Options, 33 + ) 34 + } 35 + 36 + /// render all `toast-container`s 37 + /// these need to be put into the main <body> (using `portal.to` from `lustre_portal` for example) 38 + /// 39 + /// the string in the tuple can be used as the `key` when portaling a keyed element 40 + /// 41 + /// ex: 42 + /// 43 + /// ```gleam 44 + /// portal.to("body", [], [ 45 + /// keyed.fragment({ 46 + /// use #(key, toasts) <- list.map(toaster.view_toasts(model.data.toasts)) 47 + /// 48 + /// #(key, toasts) 49 + /// }), 50 + /// ]), 51 + /// ``` 52 + /// 53 + /// 54 + pub fn view_toasts(toasts: List(Toast)) -> List(#(String, element.Element(a))) { 55 + let placements = list.group(toasts, fn(toast) { toast.options.placement }) 56 + 57 + use placement <- list.map(dict.keys(placements)) 58 + 59 + let toasts = 60 + html.div( 61 + [ 62 + attribute.class("toast-container"), 63 + attribute.popover("manual"), 64 + attribute.attribute( 65 + "data-placement", 66 + placement |> placement_to_string(), 67 + ), 68 + ], 69 + dict.get(placements, placement) 70 + |> result.unwrap([]) 71 + |> list.map(view_toast), 72 + ) 73 + #(placement |> placement_to_string(), toasts) 74 + } 75 + 76 + /// renders a toast without its surrounding `toast-container` 77 + /// 78 + fn view_toast(toast: Toast) -> element.Element(a) { 79 + let Toast(title:, message:, options:, ..) = toast 80 + 81 + element.element( 82 + "output", 83 + [ 84 + attribute.attribute("data-variant", options.variant |> variant_to_string), 85 + attribute.class("toast"), 86 + ], 87 + [ 88 + case title { 89 + option.Some(title) -> 90 + html.h6([attribute.class("toast-title")], [element.text(title)]) 91 + option.None -> element.none() 92 + }, 93 + html.div([attribute.class("toast-message")], [element.text(message)]), 94 + ], 95 + ) 96 + } 97 + 98 + fn variant_to_string(variant: toast.Variant) -> String { 99 + case variant { 100 + toast.Info -> "info" 101 + toast.Success -> "success" 102 + toast.Danger -> "danger" 103 + toast.Warning -> "warning" 104 + } 105 + } 106 + 107 + fn placement_to_string(placement: toast.Placement) -> String { 108 + case placement { 109 + toast.TopRight -> "top-right" 110 + toast.TopLeft -> "top-left" 111 + toast.TopCenter -> "top-center" 112 + toast.BottomLeft -> "bottom-left" 113 + toast.BottomRight -> "bottom-right" 114 + toast.BottomCenter -> "bottom-center" 115 + } 116 + }