1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-04-02 04:05:52 +02:00

🔤 examples+best paractises

This commit is contained in:
Jan Andrle 2025-03-17 12:53:50 +01:00
parent 2f57f115a0
commit 330f702409
Signed by: jaandrle
GPG Key ID: B3A25AED155AFFAB
3 changed files with 31 additions and 59 deletions

View File

@ -23,14 +23,6 @@ export function DataDashboard() {
conversion: [2.9, 3.5, 3.7, 2.6, 3.4, 3.5, 2.8, 2.8, 2.8, 3.1, 3.0, 2.7], conversion: [2.9, 3.5, 3.7, 2.6, 3.4, 3.5, 2.8, 2.8, 2.8, 3.1, 3.0, 2.7],
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
}; };
// Application state
const selectedYear = S(2024);
const selectedDataType = S(/** @type {'sales' | 'visitors' | 'conversion'} */ ('sales'));
const isLoading = S(false);
const error = S(null);
// Filter options
const years = [2022, 2023, 2024]; const years = [2022, 2023, 2024];
const dataTypes = [ const dataTypes = [
{ id: 'sales', label: 'Sales', unit: 'K' }, { id: 'sales', label: 'Sales', unit: 'K' },
@ -38,42 +30,32 @@ export function DataDashboard() {
{ id: 'conversion', label: 'Conversion Rate', unit: '%' } { id: 'conversion', label: 'Conversion Rate', unit: '%' }
]; ];
// Computed values // Filter options
const selectedData = S(() => { const selectedYear = S(2024);
return DATA[selectedDataType.get()];
});
const currentDataType = S(() => {
return dataTypes.find(type => type.id === selectedDataType.get());
});
const totalValue = S(() => {
const data = selectedData.get();
return data.reduce((sum, value) => sum + value, 0);
});
const averageValue = S(() => {
const data = selectedData.get();
return data.reduce((sum, value) => sum + value, 0) / data.length;
});
const highestValue = S(() => {
return Math.max(...selectedData.get());
});
// Event handlers
const onYearChange = on("change", e => { const onYearChange = on("change", e => {
selectedYear.set(parseInt(/** @type {HTMLSelectElement} */(e.target).value)); selectedYear.set(parseInt(/** @type {HTMLSelectElement} */(e.target).value));
loadData(); loadData();
}); });
const selectedDataType = S(/** @type {'sales' | 'visitors' | 'conversion'} */ ('sales'));
const onDataTypeChange = on("click", e => { const onDataTypeChange = on("click", e => {
const type = /** @type {'sales' | 'visitors' | 'conversion'} */( const type = /** @type {'sales' | 'visitors' | 'conversion'} */(
/** @type {HTMLButtonElement} */(e.currentTarget).dataset.type); /** @type {HTMLButtonElement} */(e.currentTarget).dataset.type);
selectedDataType.set(type); selectedDataType.set(type);
}); });
const currentDataType = S(() => dataTypes.find(type => type.id === selectedDataType.get()));
const selectedData = S(() => DATA[selectedDataType.get()]);
// Values based on filters
const totalValue = S(() => selectedData.get().reduce((sum, value) => sum + value, 0));
const averageValue = S(() => {
const data = selectedData.get();
return data.reduce((sum, value) => sum + value, 0) / data.length;
});
const highestValue = S(() => Math.max(...selectedData.get()));
// Simulate data loading // Simulate data loading
const isLoading = S(false);
const error = S(null);
function loadData() { function loadData() {
isLoading.set(true); isLoading.set(true);
error.set(null); error.set(null);
@ -114,7 +96,7 @@ export function DataDashboard() {
// Draw grid labels // Draw grid labels
ctx.fillStyle = '#999'; ctx.fillStyle = '#999';
ctx.font = '12px Arial'; ctx.font = '12px Arial';
ctx.fillText(Math.round(maxValue * (i / 5)), 20, y + 5); ctx.fillText(Math.round(maxValue * (i / 5)).toString(), 20, y + 5);
} }
ctx.stroke(); ctx.stroke();
@ -154,7 +136,6 @@ export function DataDashboard() {
) )
), ),
// Error message (only shown when there's an error)
S.el(error, errorMsg => !errorMsg S.el(error, errorMsg => !errorMsg
? el() ? el()
: el("div", { className: "error-message" }).append( : el("div", { className: "error-message" }).append(
@ -163,7 +144,6 @@ export function DataDashboard() {
), ),
), ),
// Loading indicator
S.el(isLoading, loading => !loading S.el(isLoading, loading => !loading
? el() ? el()
: el("div", { className: "loading-spinner" }) : el("div", { className: "loading-spinner" })

View File

@ -27,25 +27,23 @@ const imagesSample = (url=> [
* @returns {HTMLElement} Gallery element * @returns {HTMLElement} Gallery element
*/ */
export function ImageGallery(images= imagesSample) { export function ImageGallery(images= imagesSample) {
// Application state
const selectedImageId = S(null);
const filterTag = S('all'); const filterTag = S('all');
const imagesToDisplay = S(() => { const imagesToDisplay = S(() => {
const tag = filterTag.get(); const tag = filterTag.get();
if (tag === 'all') return images; if (tag === 'all') return images;
else return images.filter(img => img.alt.toLowerCase() === tag); else return images.filter(img => img.alt.toLowerCase() === tag);
}) })
const onFilterChange = tag => on("click", () => {
filterTag.set(tag);
});
// Derived state // Lightbox
const selectedImageId = S(null);
const selectedImage = S(() => { const selectedImage = S(() => {
const id = selectedImageId.get(); const id = selectedImageId.get();
return id ? images.find(img => img.id === id) : null; return id ? images.find(img => img.id === id) : null;
}); });
const isLightboxOpen = S(() => selectedImage.get() !== null); const isLightboxOpen = S(() => selectedImage.get() !== null);
// Event handlers
const onImageClick = id => on("click", () => { const onImageClick = id => on("click", () => {
selectedImageId.set(id); selectedImageId.set(id);
document.body.style.overflow = 'hidden'; // Prevent scrolling when lightbox is open document.body.style.overflow = 'hidden'; // Prevent scrolling when lightbox is open
@ -76,9 +74,6 @@ export function ImageGallery(images= imagesSample) {
const nextIndex = (currentIndex + 1) % images.length; const nextIndex = (currentIndex + 1) % images.length;
selectedImageId.set(images[nextIndex].id); selectedImageId.set(images[nextIndex].id);
}; };
const onFilterChange = tag => on("click", () => {
filterTag.set(tag);
});
// Keyboard navigation handler // Keyboard navigation handler
function handleKeyDown(e) { function handleKeyDown(e) {

View File

@ -73,8 +73,17 @@ export function Form({ initial }) {
this.value[key] = value; this.value[key] = value;
} }
}); });
/**
* Event handler for input events
* @param {"value"|"checked"} prop
* @returns {(ev: Event) => void}
* */
const onChange= prop => ev => {
const input = /** @type {HTMLInputElement} */(ev.target);
S.action(formState, "update", /** @type {keyof FormState} */(input.id), input[prop]);
};
// Derived signals for validation // Form validate state
const nameValid = S(() => formState.get().name.length >= 3); const nameValid = S(() => formState.get().name.length >= 3);
const emailValid = S(() => { const emailValid = S(() => {
const email = formState.get().email; const email = formState.get().email;
@ -89,8 +98,6 @@ export function Form({ initial }) {
return password === confirmPassword && confirmPassword !== ''; return password === confirmPassword && confirmPassword !== '';
}); });
const termsAgreed = S(() => formState.get().agreedToTerms); const termsAgreed = S(() => formState.get().agreedToTerms);
// Overall form validity
const formValid = S(() => const formValid = S(() =>
nameValid.get() && nameValid.get() &&
emailValid.get() && emailValid.get() &&
@ -99,16 +106,6 @@ export function Form({ initial }) {
termsAgreed.get() termsAgreed.get()
); );
// Event handlers
/**
* Event handler for input events
* @param {"value"|"checked"} prop
* @returns {(ev: Event) => void}
* */
const onChange= prop => ev => {
const input = /** @type {HTMLInputElement} */(ev.target);
S.action(formState, "update", /** @type {keyof FormState} */(input.id), input[prop]);
};
const dispatcSubmit = dispatchEvent("form:submit", host); const dispatcSubmit = dispatchEvent("form:submit", host);
const onSubmit = on("submit", e => { const onSubmit = on("submit", e => {
e.preventDefault(); e.preventDefault();