diff --git a/asset/css/callout.less b/asset/css/callout.less new file mode 100644 index 00000000..fcb4fabf --- /dev/null +++ b/asset/css/callout.less @@ -0,0 +1,66 @@ +// Layout +.callout { + display: flex; + justify-content: center; + column-gap: 1em; + + width: fit-content; + margin: 0 auto 1em; + + &.callout-fullwidth { + width: 100%; + justify-content: start; + } + + &.form-callout { + margin-left: 14em; + width: auto; + justify-content: start; + } + + i.icon::before { + margin-right: 0; + } + + p { + margin: 0; + } + + .callout-title { + margin-bottom: .5em; + } + + .callout-text { + display: flex; + flex-direction: column; + } +} + +// Style +.callout { + padding: .5em 1em; + border: 1px solid var(--callout-color); + background-color: color-mix(in srgb, var(--callout-color) 10%, transparent); + border-radius: .25em; + + i.icon { + color: var(--callout-color); + font-size: 1.5em; + } + + &.callout-type-info { + --callout-color: @color-pending; + } + + &.callout-type-success { + --callout-color: @color-ok; + } + + &.callout-type-warning { + --callout-color: @color-warning; + } + + &.callout-type-error { + --callout-color: @color-critical; + } +} diff --git a/src/Common/CalloutType.php b/src/Common/CalloutType.php new file mode 100644 index 00000000..b8d58cc0 --- /dev/null +++ b/src/Common/CalloutType.php @@ -0,0 +1,31 @@ + 'circle-info', + self::Success => 'circle-check', + self::Warning => 'warning', + self::Error => 'circle-xmark', + }); + } +} diff --git a/src/Widget/Callout.php b/src/Widget/Callout.php new file mode 100644 index 00000000..b3bf9f3f --- /dev/null +++ b/src/Widget/Callout.php @@ -0,0 +1,95 @@ + 'callout']; + + /** + * Create a new callout + * + * @param CalloutType $type the type of the callout. The type determines the color and icon that is used. + * @param ValidHtml|string $content the content of the callout + * @param string|null $title an optional title, displayed above the content + */ + public function __construct( + protected CalloutType $type, + protected ValidHtml|string $content, + protected ?string $title = null + ) { + $this->addAttributes(Attributes::create(['class' => $type->value])); + } + + public function assemble(): void + { + $this->addHtml($this->type->getIcon()); + + $this->addHtml(HtmlElement::create( + 'div', + ['class' => 'callout-text'], + [ + $this->title + ? HtmlElement::create('strong', ['class' => 'callout-title'], new Text($this->title)) + : null, + is_string($this->content) ? new Text($this->content) : $this->content, + ], + )); + } + + /** + * Callouts are only as wide as their content. + * Setting it to fullwidth will force the callout to be as wide as its container. + * + * @param bool $fullwidth should the callout be fullwidth + * + * @return $this + */ + public function setFullwidth(bool $fullwidth = true): static + { + if ($fullwidth) { + $this->addAttributes(Attributes::create(['class' => 'callout-fullwidth'])); + } else { + $this->removeAttribute('class', 'callout-fullwidth'); + } + + return $this; + } + + /** + * Setting this to true will allow the callout to be used for a single form element. + * This is used to visually align the callout to the content of the form element. + * + * @param bool $isFormElement should the callout be used for a form element + * + * @return $this + */ + public function setFormElement(bool $isFormElement = true): static + { + if ($isFormElement) { + $this->addAttributes(Attributes::create(['class' => 'form-callout'])); + } else { + $this->removeAttribute('class', 'from-callout'); + } + + return $this; + } +}