Skip to content

Commit

Permalink
chore(vanilla): add avatar example
Browse files Browse the repository at this point in the history
  • Loading branch information
segunadebayo committed May 25, 2024
1 parent eb71907 commit a993fb8
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 37 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-coats-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zag-js/avatar": patch
---

Improve image load check to use `naturalWidth|Height` instead of `currentSrc`
2 changes: 1 addition & 1 deletion .xstate/avatar.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const fetchMachine = createMachine({
states: {
loading: {
activities: ["trackSrcChange"],
entry: ["checkImgStatus"],
entry: ["checkImageStatus"],
on: {
"IMG.LOADED": {
target: "loaded",
Expand Down
22 changes: 3 additions & 19 deletions examples/vanilla-ts/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,9 @@
<title>Vite + TS</title>
</head>
<body style="padding: 40px">
<h1>Accordion</h1>
<h1>Vanilla + Zag</h1>

<div class="accordion">
<div class="accordion-item" data-value="a">
<button class="accordion-trigger">First Button</button>
<div class="accordion-content">First Content</div>
</div>

<div class="accordion-item" data-value="b">
<button class="accordion-trigger">Second Button</button>
<div class="accordion-content">Second Content</div>
</div>

<div class="accordion-item" data-value="c">
<button class="accordion-trigger">Third Button</button>
<div class="accordion-content">Third Content</div>
</div>
</div>

<script type="module" src="/src/main.ts"></script>
<a href="pages/accordion.html">Accordion</a>
<a href="pages/avatar.html">Avatar</a>
</body>
</html>
31 changes: 31 additions & 0 deletions examples/vanilla-ts/pages/accordion.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + TS</title>
</head>
<body style="padding: 40px">
<h1>Accordion</h1>

<div class="accordion">
<div class="accordion-item" data-value="a">
<button class="accordion-trigger">First Button</button>
<div class="accordion-content">First Content</div>
</div>

<div class="accordion-item" data-value="b">
<button class="accordion-trigger">Second Button</button>
<div class="accordion-content">Second Content</div>
</div>

<div class="accordion-item" data-value="c">
<button class="accordion-trigger">Third Button</button>
<div class="accordion-content">Third Content</div>
</div>
</div>

<script type="module" src="./accordion.ts"></script>
</body>
</html>
9 changes: 9 additions & 0 deletions examples/vanilla-ts/pages/accordion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import "@zag-js/shared/src/style.css"

import { Accordion } from "../src/accordion"
import { nanoid } from "nanoid"

document.querySelectorAll<HTMLElement>(".accordion").forEach((rootEl) => {
const accordion = new Accordion(rootEl, { id: nanoid(), multiple: true })
accordion.init()
})
24 changes: 24 additions & 0 deletions examples/vanilla-ts/pages/avatar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + TS</title>
</head>
<body style="padding: 40px">
<h1>Avatar</h1>

<div class="avatar">
<span class="avatar-fallback">PA</span>
<img
class="avatar-image"
alt="Naruto"
referrerpolicy="no-referrer"
src="https://static.wikia.nocookie.net/naruto/images/d/d6/Naruto_Part_I.png/revision/latest/top-crop/width/200/height/150?cb=20210223094656"
/>
</div>

<script type="module" src="./avatar.ts"></script>
</body>
</html>
9 changes: 9 additions & 0 deletions examples/vanilla-ts/pages/avatar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import "@zag-js/shared/src/style.css"

import { Avatar } from "../src/avatar"
import { nanoid } from "nanoid"

document.querySelectorAll<HTMLElement>(".avatar").forEach((rootEl) => {
const avatar = new Avatar(rootEl, { id: nanoid() })
avatar.init()
})
7 changes: 4 additions & 3 deletions examples/vanilla-ts/src/accordion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ export class Accordion {
service: ReturnType<typeof accordion.machine>
api: accordion.Api<any>

constructor(root: string, context: accordion.Context) {
const rootEl = document.querySelector<HTMLElement>(root)

constructor(rootEl: HTMLElement | null, context: accordion.Context) {
if (!rootEl) throw new Error("Root element not found")
this.rootEl = rootEl

Expand All @@ -21,6 +19,9 @@ export class Accordion {

init = () => {
const { service } = this

this.render()

this.service.subscribe(() => {
this.api = accordion.connect(service.state, service.send, normalizeProps)
this.render()
Expand Down
45 changes: 45 additions & 0 deletions examples/vanilla-ts/src/avatar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as avatar from "@zag-js/avatar"
import { normalizeProps } from "./normalize-props"
import { spreadProps } from "./spread-props"

export class Avatar {
rootEl: HTMLElement
service: ReturnType<typeof avatar.machine>
api: avatar.Api<any>

constructor(rootEl: HTMLElement | null, context: avatar.Context) {
if (!rootEl) throw new Error("Root element not found")
this.rootEl = rootEl

this.service = avatar.machine(context)
this.api = avatar.connect(this.service.state, this.service.send, normalizeProps)
}

private disposable = new Map<HTMLElement, VoidFunction>()

init = () => {
const { service } = this
this.render()
this.service.subscribe(() => {
this.api = avatar.connect(service.state, service.send, normalizeProps)
this.render()
})

this.service.start()
}

destroy = () => {
this.service.stop()
}

render = () => {
const rootEl = this.rootEl
this.disposable.set(rootEl, spreadProps(this.rootEl, this.api.rootProps))

const imageEl = rootEl.querySelector<HTMLElement>(".avatar-image")
if (imageEl) this.disposable.set(imageEl, spreadProps(imageEl, this.api.imageProps))

const fallbackEl = rootEl.querySelector<HTMLElement>(".avatar-fallback")
if (fallbackEl) this.disposable.set(fallbackEl, spreadProps(fallbackEl, this.api.fallbackProps))
}
}
9 changes: 0 additions & 9 deletions examples/vanilla-ts/src/main.ts

This file was deleted.

14 changes: 9 additions & 5 deletions packages/machines/avatar/src/avatar.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function machine(userContext: UserDefinedContext) {
states: {
loading: {
activities: ["trackSrcChange"],
entry: ["checkImgStatus"],
entry: ["checkImageStatus"],
on: {
"IMG.LOADED": {
target: "loaded",
Expand Down Expand Up @@ -89,14 +89,18 @@ export function machine(userContext: UserDefinedContext) {
invokeOnError(ctx) {
ctx.onStatusChange?.({ status: "error" })
},
checkImgStatus(ctx, _evt, { send }) {
const img = dom.getImageEl(ctx)
if (img?.complete) {
const type = img.currentSrc ? "IMG.LOADED" : "IMG.ERROR"
checkImageStatus(ctx, _evt, { send }) {
const imageEl = dom.getImageEl(ctx)
if (imageEl?.complete) {
const type = hasLoaded(imageEl) ? "IMG.LOADED" : "IMG.ERROR"
send({ type, src: "ssr" })
}
},
},
},
)
}

function hasLoaded(image: HTMLImageElement) {
return image.complete && image.naturalWidth !== 0 && image.naturalHeight !== 0
}

0 comments on commit a993fb8

Please sign in to comment.