init
This commit is contained in:
33
components/AddButton.js
Normal file
33
components/AddButton.js
Normal file
@@ -0,0 +1,33 @@
|
||||
class AddButton extends Shadow {
|
||||
render() {
|
||||
p("+")
|
||||
.fontWeight("bolder")
|
||||
.paddingVertical(1, em)
|
||||
.boxSizing("border-box")
|
||||
.paddingHorizontal(1.25, em)
|
||||
.background("var(--quillred)")
|
||||
.color("var(--headertext)")
|
||||
.marginBottom(1, em)
|
||||
.border("1px solid var(--headertext)")
|
||||
.borderRadius(100, px)
|
||||
.onTap(() => {
|
||||
this.handleAdd()
|
||||
})
|
||||
}
|
||||
|
||||
handleAdd() {
|
||||
const app = document.documentElement.attr("app")
|
||||
switch (app) {
|
||||
case "jobs":
|
||||
$("jobform-").toggle()
|
||||
break;
|
||||
case "calendar":
|
||||
$("eventform-").toggle()
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(AddButton)
|
||||
8
components/AppTitle.js
Normal file
8
components/AppTitle.js
Normal file
@@ -0,0 +1,8 @@
|
||||
function AppTitle(text) {
|
||||
return h1(text)
|
||||
.fontFamily("Laandbrau")
|
||||
.letterSpacing(2, px)
|
||||
.fontSize(2.3, em)
|
||||
}
|
||||
|
||||
window.AppTitle = AppTitle
|
||||
36
components/Avatar.js
Normal file
36
components/Avatar.js
Normal file
@@ -0,0 +1,36 @@
|
||||
class Avatar extends Shadow {
|
||||
constructor(person, size) {
|
||||
super()
|
||||
this.person = person
|
||||
this.size = size
|
||||
}
|
||||
|
||||
render() {
|
||||
const { person, size } = this
|
||||
if (person.image_path) {
|
||||
img(`${config.SERVER}${person.image_path}`, `${size}em`, `${size}em`)
|
||||
.borderRadius(50, pct)
|
||||
.objectFit("cover")
|
||||
.flexShrink(0)
|
||||
} else {
|
||||
const initials = [person.first_name?.[0], person.last_name?.[0]].filter(Boolean).join("").toUpperCase()
|
||||
VStack(() => {
|
||||
p(initials)
|
||||
.margin(0).fontSize(size * 0.35, em).fontWeight("700")
|
||||
.color("white").lineHeight("1")
|
||||
})
|
||||
.width(size, em).height(size, em).borderRadius(50, pct)
|
||||
.background(this.avatarColor(`${person.first_name} ${person.last_name}`))
|
||||
.justifyContent("center").alignItems("center").flexShrink(0)
|
||||
}
|
||||
}
|
||||
|
||||
avatarColor(name) {
|
||||
const colors = ["#3b82f6", "#ef4444", "#10b981", "#f59e0b", "#8b5cf6", "#ec4899", "#06b6d4", "#84cc16"]
|
||||
let hash = 0
|
||||
for (let i = 0; i < (name || "").length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash)
|
||||
return colors[Math.abs(hash) % colors.length]
|
||||
}
|
||||
}
|
||||
|
||||
register(Avatar)
|
||||
62
components/BackButton.js
Normal file
62
components/BackButton.js
Normal file
@@ -0,0 +1,62 @@
|
||||
class BackButton extends Shadow {
|
||||
constructor(withBackground = true, isDown = false, toggle) {
|
||||
super()
|
||||
this.withBackground = withBackground;
|
||||
this.isDown = isDown;
|
||||
this.toggle = toggle;
|
||||
}
|
||||
|
||||
render() {
|
||||
const toggle = this.toggle;
|
||||
const btn = div("➩")
|
||||
.display("flex")
|
||||
.fontSize(2, em)
|
||||
.paddingTop(0, rem)
|
||||
.paddingHorizontal(0.5, rem)
|
||||
.paddingBottom(0.9, rem)
|
||||
.zIndex(3)
|
||||
.color("var(--darkaccent)")
|
||||
.transform(this.isDown ? "rotate(90deg)" : "rotate(180deg)")
|
||||
.transition("scale .2s, color .2s")
|
||||
.onTouch(function (start, e) {
|
||||
if (start) {
|
||||
this.scale("1.5")
|
||||
this.style.color = "var(--quillred)"
|
||||
} else {
|
||||
this.scale("")
|
||||
this.style.color = "var(--darkaccent)"
|
||||
e.stopPropagation();
|
||||
toggle();
|
||||
}
|
||||
})
|
||||
|
||||
if (this.withBackground) {
|
||||
btn
|
||||
.width(3, rem)
|
||||
.height(3, rem)
|
||||
.padding(0)
|
||||
.borderRadius(50, pct)
|
||||
.color("var(--divider)")
|
||||
.background("var(--darkaccent)")
|
||||
.color()
|
||||
.border("1.5px solid var(--divider)")
|
||||
.alignItems("center")
|
||||
.justifyContent("center")
|
||||
.onTouch(function (start, e) {
|
||||
if (start) {
|
||||
this.scale("1.5")
|
||||
this.color("var(--darkaccent)")
|
||||
this.backgroundColor("var(--divider)")
|
||||
} else {
|
||||
this.color("var(--divider)")
|
||||
this.backgroundColor("var(--darkaccent)")
|
||||
this.scale("")
|
||||
e.stopPropagation();
|
||||
toggle();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(BackButton)
|
||||
193
components/BottomSheet.js
Normal file
193
components/BottomSheet.js
Normal file
@@ -0,0 +1,193 @@
|
||||
css(`
|
||||
bottomsheet- {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
bottomsheet- .VStack::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
bottomsheet- .VStack::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
bottomsheet- .VStack::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
`)
|
||||
|
||||
class BottomSheet extends Shadow {
|
||||
swipeDragStartX = null;
|
||||
swipeDragStartY = null;
|
||||
swipeDragStartTime = null;
|
||||
isSwiping = false;
|
||||
swipeAxisLocked = false;
|
||||
swipeIsVertical = false;
|
||||
swipeTranslate = 0;
|
||||
|
||||
SWIPE_COMMIT_DISTANCE = window.outerHeight * 0.25;
|
||||
SWIPE_VELOCITY_THRESHOLD = 0.4;
|
||||
|
||||
renderContent = null;
|
||||
_closeOverride = null;
|
||||
|
||||
constructor(zOffset = 0) {
|
||||
super()
|
||||
this.zOffset = zOffset;
|
||||
}
|
||||
|
||||
show(renderFn, onClose = null) {
|
||||
this._closeOverride = null;
|
||||
this.renderContent = renderFn;
|
||||
this._onClose = onClose;
|
||||
this.rerender();
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => this.setSheet(true)));
|
||||
}
|
||||
|
||||
replace(renderFn) {
|
||||
this.renderContent = renderFn;
|
||||
this.rerender();
|
||||
if (this.sheetEl) {
|
||||
this.sheetEl.style.transition = "none";
|
||||
this.sheetEl.style.top = "7.5%";
|
||||
this.sheetEl.style.pointerEvents = "auto";
|
||||
}
|
||||
if (this.overlayEl) {
|
||||
this.overlayEl.style.transition = "none";
|
||||
this.overlayEl.style.background = "rgba(0, 0, 0, 0.3)";
|
||||
this.overlayEl.style.pointerEvents = "auto";
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
ZStack(() => {
|
||||
this.overlayEl = ZStack()
|
||||
.position("fixed")
|
||||
.inset(0)
|
||||
.zIndex(90 + this.zOffset)
|
||||
.background("transparent")
|
||||
.pointerEvents("none")
|
||||
.onTap(() => this.toggle())
|
||||
|
||||
this.sheetEl = ZStack(() => {
|
||||
VStack(() => {
|
||||
if (this.renderContent) this.renderContent()
|
||||
})
|
||||
.height(100, pct)
|
||||
.position("relative")
|
||||
})
|
||||
.position("fixed")
|
||||
.height(92.5, pct)
|
||||
.top(100, pct)
|
||||
.left(0).right(0)
|
||||
.zIndex(100 + this.zOffset)
|
||||
.background("var(--main)")
|
||||
.borderTopLeftRadius("10px").borderTopRightRadius("10px")
|
||||
.boxSizing("border-box")
|
||||
.transition("top .3s")
|
||||
.pointerEvents("none")
|
||||
.onTouch((start, e) => this.handleSwipeTouch(start, e))
|
||||
})
|
||||
}
|
||||
|
||||
get isOpen() {
|
||||
return this.sheetEl?.style.top === "7.5%";
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.setSheet(!this.isOpen);
|
||||
}
|
||||
|
||||
forceClose() {
|
||||
this._closeOverride = null
|
||||
this.setSheet(false)
|
||||
}
|
||||
|
||||
setSheet(open) {
|
||||
if (!open && this._closeOverride) { this._closeOverride(); return }
|
||||
if (this.overlayEl) {
|
||||
this.overlayEl.style.transition = "background 0.3s";
|
||||
this.overlayEl.style.pointerEvents = open ? "auto" : "none";
|
||||
this.overlayEl.style.background = open ? "rgba(0, 0, 0, 0.3)" : "transparent";
|
||||
}
|
||||
if (this.sheetEl) {
|
||||
this.sheetEl.style.transition = "top 0.3s";
|
||||
this.sheetEl.style.top = open ? "7.5%" : "100%";
|
||||
this.sheetEl.style.pointerEvents = open ? "auto" : "none";
|
||||
}
|
||||
if (!open) {
|
||||
if (this._onClose) {
|
||||
const cb = this._onClose;
|
||||
this._onClose = null;
|
||||
setTimeout(cb, 300);
|
||||
}
|
||||
this.isSwiping = false;
|
||||
this.swipeTranslate = 0;
|
||||
this.swipeDragStartX = null;
|
||||
this.swipeDragStartY = null;
|
||||
document.removeEventListener("touchmove", this.onSwipeMove);
|
||||
}
|
||||
}
|
||||
|
||||
handleSwipeTouch(start, e) {
|
||||
if (start) {
|
||||
if (this.sheetEl?.style.top !== "7.5%") return;
|
||||
e.stopPropagation();
|
||||
this.swipeDragStartX = e.touches[0].clientX;
|
||||
this.swipeDragStartY = e.touches[0].clientY;
|
||||
this.swipeDragStartTime = Date.now();
|
||||
this.isSwiping = true;
|
||||
this.swipeAxisLocked = false;
|
||||
this.swipeIsVertical = false;
|
||||
document.addEventListener("touchmove", this.onSwipeMove, { passive: false });
|
||||
} else {
|
||||
if (!this.isSwiping) return;
|
||||
document.removeEventListener("touchmove", this.onSwipeMove);
|
||||
|
||||
if (!this.swipeIsVertical) {
|
||||
this.isSwiping = false;
|
||||
this.setSheet(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const delta = e.changedTouches[0].clientY - this.swipeDragStartY;
|
||||
const velocity = Math.abs(delta) / (Date.now() - this.swipeDragStartTime);
|
||||
const shouldCommit = delta > 0 && (delta > this.SWIPE_COMMIT_DISTANCE || velocity > this.SWIPE_VELOCITY_THRESHOLD);
|
||||
|
||||
this.swipeTranslate = 0;
|
||||
this.setSheet(!shouldCommit);
|
||||
|
||||
this.isSwiping = false;
|
||||
this.swipeDragStartX = null;
|
||||
this.swipeDragStartY = null;
|
||||
}
|
||||
}
|
||||
|
||||
onSwipeMove = (e) => {
|
||||
if (!this.isSwiping) return;
|
||||
|
||||
const dx = e.touches[0].clientX - this.swipeDragStartX;
|
||||
const dy = e.touches[0].clientY - this.swipeDragStartY;
|
||||
|
||||
if (!this.swipeAxisLocked) {
|
||||
if (Math.abs(dx) < 5 && Math.abs(dy) < 5) return;
|
||||
this.swipeAxisLocked = true;
|
||||
this.swipeIsVertical = Math.abs(dy) > Math.abs(dx);
|
||||
}
|
||||
if (!this.swipeIsVertical) return;
|
||||
|
||||
const delta = e.touches[0].clientY - this.swipeDragStartY;
|
||||
const scrollEl = this.querySelector(".VStack");
|
||||
if (delta <= 0 || (scrollEl && scrollEl.scrollTop > 0)) return;
|
||||
|
||||
e.preventDefault();
|
||||
this.swipeTranslate = delta;
|
||||
this.sheetEl.style.transition = "";
|
||||
this.sheetEl.style.top = `calc(7.5% + ${delta}px)`;
|
||||
}
|
||||
}
|
||||
|
||||
register(BottomSheet)
|
||||
72
components/SearchBar.js
Normal file
72
components/SearchBar.js
Normal file
@@ -0,0 +1,72 @@
|
||||
css(`
|
||||
searchbar- input::placeholder {
|
||||
color: #5C504D;
|
||||
}
|
||||
`)
|
||||
|
||||
class SearchBar extends Shadow {
|
||||
searchText
|
||||
width
|
||||
|
||||
constructor(searchText, width) {
|
||||
super()
|
||||
this.searchText = searchText
|
||||
this.width = width
|
||||
}
|
||||
|
||||
render() {
|
||||
form(() => {
|
||||
input("Search", this.width)
|
||||
.attr({ name: "searchText", type: "text" })
|
||||
.attr({ value: this.searchText ? this.searchText : "" })
|
||||
.paddingVertical(0.75, em)
|
||||
.boxSizing("border-box")
|
||||
.paddingHorizontal(1, em)
|
||||
.background("var(--searchbackground)")
|
||||
.color("gray")
|
||||
.marginBottom(1, em)
|
||||
.marginLeft(1, em)
|
||||
.marginRight(0.5, em)
|
||||
.border("1px solid color-mix(in srgb, var(--accent) 60%, transparent)")
|
||||
.borderRadius(100, px)
|
||||
.fontFamily("Arial")
|
||||
.fontSize(1, em)
|
||||
.cursor("not-allowed")
|
||||
.onTouch(function (start) {
|
||||
if (start) {
|
||||
this.style.backgroundColor = "var(--accent)"
|
||||
} else {
|
||||
this.style.backgroundColor = "var(--searchbackground)"
|
||||
}
|
||||
})
|
||||
})
|
||||
.onSubmit(async (e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
this.dispatchSearchEvent(data.get("searchText"))
|
||||
})
|
||||
}
|
||||
|
||||
dispatchSearchEvent(searchText) {
|
||||
const app = document.documentElement.attr("app")
|
||||
switch (app) {
|
||||
case "Jobs":
|
||||
window.dispatchEvent(new CustomEvent('jobsearch', {
|
||||
detail: { searchText: searchText }
|
||||
}));
|
||||
break;
|
||||
case "Events":
|
||||
window.dispatchEvent(new CustomEvent('eventsearch', {
|
||||
detail: { searchText: searchText }
|
||||
}));
|
||||
break;
|
||||
case "Announcements":
|
||||
window.dispatchEvent(new CustomEvent('announcementsearch', {
|
||||
detail: { searchText: searchText }
|
||||
}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(SearchBar)
|
||||
Reference in New Issue
Block a user