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:
parent
2f57f115a0
commit
330f702409
@ -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" })
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user