scanfile/server/web/templui/components/avatar/avatar.templ
2025-06-03 15:44:56 +02:00

291 lines
5.5 KiB
Text

// templui component avatar - version: main installed by templui v0.71.0
package avatar
import (
"fmt"
"git.jmbit.de/jmb/scanfile/server/web/templui/utils"
"strings"
)
type Size string
type GroupSpacing string
const (
SizeSm Size = "sm"
SizeMd Size = "md"
SizeLg Size = "lg"
)
const (
GroupSpacingSm GroupSpacing = "sm"
GroupSpacingMd GroupSpacing = "medium"
GroupSpacingLg GroupSpacing = "large"
)
type Props struct {
ID string
Class string
Attributes templ.Attributes
Size Size
InGroup bool
}
type ImageProps struct {
ID string
Class string
Attributes templ.Attributes
Alt string
Src string
}
type FallbackProps struct {
ID string
Class string
Attributes templ.Attributes
}
type GroupProps struct {
ID string
Class string
Attributes templ.Attributes
Spacing GroupSpacing
}
templ Avatar(props ...Props) {
@Script()
{{ var p Props }}
if len(props) > 0 {
{{ p = props[0] }}
}
<div
data-avatar
if p.ID != "" {
id={ p.ID }
}
class={
utils.TwMerge(
"inline-flex items-center justify-center",
SizeClasses(p.Size),
"rounded-full bg-muted",
utils.If(p.InGroup, "ring-2 ring-background"),
p.Class,
),
}
{ p.Attributes... }
>
{ children... }
</div>
}
templ Image(props ...ImageProps) {
{{ var p ImageProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<img
data-avatar-image
if p.ID != "" {
id={ p.ID }
}
if p.Src != "" {
src={ p.Src }
}
alt={ avatarAlt(p) }
class={
utils.TwMerge(
"w-full h-full",
"rounded-full object-cover",
p.Class,
),
}
{ p.Attributes... }
/>
}
templ Fallback(props ...FallbackProps) {
{{ var p FallbackProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<span
data-avatar-fallback
if p.ID != "" {
id={ p.ID }
}
class={
utils.TwMerge(
"font-medium text-muted-foreground",
p.Class,
),
}
{ p.Attributes... }
>
{ children... }
</span>
}
templ Group(props ...GroupProps) {
{{ var p GroupProps }}
if len(props) > 0 {
{{ p = props[0] }}
}
<div
if p.ID != "" {
id={ p.ID }
}
class={
utils.TwMerge(
"flex items-center -space-x-3",
groupSpacingClasses(p.Spacing),
p.Class,
),
}
{ p.Attributes... }
>
{ children... }
</div>
}
templ GroupOverflow(count int, props ...Props) {
{{ var p Props }}
if len(props) > 0 {
{{ p = props[0] }}
}
<div
if p.ID != "" {
id={ p.ID }
}
class={
utils.TwMerge(
"inline-flex items-center justify-center",
SizeClasses(p.Size),
"rounded-full bg-muted ring-2 ring-background",
p.Class,
),
}
{ p.Attributes... }
>
<span class="text-xs font-medium">+{ fmt.Sprint(count) }</span>
</div>
}
func SizeClasses(size Size) string {
switch size {
case SizeSm:
return "w-8 h-8 text-xs"
case SizeLg:
return "w-16 h-16 text-xl"
default: // SizeMd
return "w-12 h-12 text-base"
}
}
func Initials(name string) string {
parts := strings.Fields(name)
initials := ""
for i, part := range parts {
if i > 1 {
break
}
if len(part) > 0 {
initials += string(part[0])
}
}
return strings.ToUpper(initials)
}
func groupSpacingClasses(spacing GroupSpacing) string {
switch spacing {
case GroupSpacingSm:
return "-space-x-1"
case GroupSpacingLg:
return "-space-x-4"
default: // GroupSpacingMd
return "-space-x-2"
}
}
func avatarAlt(p ImageProps) string {
if p.Alt != "" {
return p.Alt
}
if p.ID != "" {
return fmt.Sprintf("Avatar of %s", p.ID)
}
if p.Src != "" {
return "User avatar"
}
return ""
}
var handle = templ.NewOnceHandle()
templ Script() {
@handle.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
(function() { // IIFE
function initAvatar(avatar) {
const image = avatar.querySelector('[data-avatar-image]');
const fallback = avatar.querySelector('[data-avatar-fallback]');
if (image && fallback) {
image.style.display = 'none';
fallback.style.display = 'none';
const showFallback = () => {
image.style.display = 'none';
fallback.style.display = '';
};
const showImage = () => {
image.style.display = '';
fallback.style.display = 'none';
};
if (image.complete) {
image.naturalWidth > 0 && image.naturalHeight > 0 ? showImage() : showFallback();
} else {
image.addEventListener('load', showImage, { once: true });
image.addEventListener('error', showFallback, { once: true });
setTimeout(() => {
if (image.complete && !(image.naturalWidth > 0 && image.naturalHeight > 0)) {
showFallback();
}
}, 50);
}
} else if (fallback) {
fallback.style.display = '';
} else if (image) {
image.style.display = '';
}
}
function initAllComponents(root = document) {
if (root instanceof Element && root.matches('[data-avatar]')) {
initAvatar(root);
}
for (const avatar of root.querySelectorAll('[data-avatar]')) {
initAvatar(avatar);
}
}
const handleHtmxSwap = (event) => {
const target = event.detail.target || event.detail.elt;
if (target instanceof Element) {
requestAnimationFrame(() => initAllComponents(target));
}
};
initAllComponents();
document.addEventListener('DOMContentLoaded', () => initAllComponents());
document.body.addEventListener('htmx:afterSwap', handleHtmxSwap);
document.body.addEventListener('htmx:oobAfterSwap', handleHtmxSwap);
})(); // End of IIFE
</script>
}
}