291 lines
5.5 KiB
Text
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>
|
|
}
|
|
}
|