Progress bar:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>Segmented Progress Bar</title>
<script src="https://cdn.tailwindcss.com"></script>
<script
defer
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
></script>
</head>
<body
class="bg-gray-100 min-h-screen flex items-center justify-center"
>
<div
x-data="progressBar()"
x-init="init()"
class="w-full max-w-3xl p-6 bg-white rounded-lg shadow-lg"
>
<div class="flex mb-4 space-x-1">
<template
x-for="(width, index) in segmentWidths"
:key="index"
>
<div
class="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden"
>
<div
class="h-full bg-blue-500 transition-all duration-300 ease-in-out"
:style="{ width: width }"
></div>
</div>
</template>
</div>
<h2
class="text-2xl font-bold text-center text-gray-800"
x-text="getCurrentStepTitle()"
></h2>
<p
class="text-center text-gray-600"
x-text="getCurrentStepDescription()"
></p>
<p
class="text-center text-gray-600 mt-2"
x-text="`${progress}% completed`"
></p>
</div>
<script>
function progressBar() {
return {
progress: 0,
steps: {},
segmentWidths: [],
init() {
this.progress = parseInt(S.progress);
this.steps = JSON.parse(S.steps);
this.calculateSegmentWidths();
},
calculateSegmentWidths() {
const stepCount = Object.keys(
this.steps
).length;
const segmentSize = 100 / stepCount;
this.segmentWidths = Array.from(
{ length: stepCount },
(_, index) => {
const segmentProgress = Math.min(
Math.max(
this.progress - index * segmentSize,
0
),
segmentSize
);
return `${
(segmentProgress / segmentSize) * 100
}%`;
}
);
},
getCurrentStepTitle() {
const currentStepIndex = Math.floor(
this.progress /
(100 / Object.keys(this.steps).length)
);
const stepKeys = Object.keys(this.steps);
return stepKeys[
Math.min(
currentStepIndex,
stepKeys.length - 1
)
];
},
getCurrentStepDescription() {
const currentStepIndex = Math.floor(
this.progress /
(100 / Object.keys(this.steps).length)
);
const stepKeys = Object.keys(this.steps);
return this.steps[
stepKeys[
Math.min(
currentStepIndex,
stepKeys.length - 1
)
]
];
},
};
}
</script>
</body>
</html>
Steps manager:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>Steps Manager</title>
<script src="https://cdn.tailwindcss.com"></script>
<script
defer
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
></script>
</head>
<body class="bg-gray-100 min-h-screen p-6">
<div
x-data="stepsManager()"
x-init="initializeSteps()"
class="container mx-auto bg-gray-100 rounded-lg shadow-lg p-6"
>
<h1
class="text-3xl font-bold mb-6 text-center text-gray-800"
>
Steps Manager
</h1>
<div class="mb-6">
<h2
class="text-xl font-semibold mb-2 text-gray-700"
>
Add New Step
</h2>
<div class="flex space-x-2">
<input
type="text"
x-model="newStepTitle"
placeholder="Step Title"
class="flex-1 p-2 border rounded"
/>
<input
type="text"
x-model="newStepDescription"
placeholder="Step Description"
class="flex-1 p-2 border rounded"
/>
<button
@click="debounceAddStep()"
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition duration-300"
>
Add Step
</button>
</div>
</div>
<div>
<h2
class="text-xl font-semibold mb-2 text-gray-700"
>
Current Steps
</h2>
<template
x-for="(step, index) in stepsArray"
:key="index"
>
<div class="bg-white p-4 rounded-lg shadow mb-2">
<div
x-show="!step.editing"
class="flex items-center"
>
<div class="flex-1">
<h3
class="font-semibold text-lg text-gray-800"
x-text="step.title"
></h3>
<p
class="text-gray-600"
x-text="step.description"
></p>
</div>
<div class="flex space-x-2">
<button
@click="editStep(index)"
class="text-blue-500 hover:text-blue-700"
>
✏️
</button>
<button
@click="debounceMoveStep(index, 'up')"
:disabled="index === 0"
class="text-blue-500 hover:text-blue-700 disabled:text-gray-400"
>
↑
</button>
<button
@click="debounceMoveStep(index, 'down')"
:disabled="index === stepsArray.length - 1"
class="text-blue-500 hover:text-blue-700 disabled:text-gray-400"
>
↓
</button>
<button
@click="debounceRemoveStep(index)"
class="text-red-500 hover:text-red-700"
>
×
</button>
</div>
</div>
<div
x-show="step.editing"
class="flex items-center space-x-2"
>
<input
type="text"
x-model="step.editTitle"
class="flex-1 p-2 border rounded"
/>
<input
type="text"
x-model="step.editDescription"
class="flex-1 p-2 border rounded"
/>
<button
@click="saveEdit(index)"
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 transition duration-300"
>
Save
</button>
<button
@click="cancelEdit(index)"
class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600 transition duration-300"
>
Cancel
</button>
</div>
</div>
</template>
</div>
<div class="mt-6">
<h2
class="text-xl font-semibold mb-2 text-gray-700"
>
JSON Output
</h2>
<pre
class="bg-gray-800 text-green-400 p-4 rounded overflow-x-auto"
x-text="JSON.stringify(steps, null, 2)"
></pre>
</div>
</div>
<script>
function debounce(func, wait) {
let timeout;
return function (...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(
() => func.apply(context, args),
wait
);
};
}
function stepsManager() {
return {
steps: {},
stepsArray: [],
newStepTitle: "",
newStepDescription: "",
initializeSteps() {
if (S && S.steps) {
try {
this.steps = JSON.parse(S.steps);
this.updateStepsArray();
} catch (error) {
console.error(
"Error parsing S.steps:",
error
);
this.steps = {};
this.stepsArray = [];
}
}
},
updateStepsArray() {
this.stepsArray = Object.entries(
this.steps
).map(([title, description]) => ({
title,
description,
editing: false,
editTitle: title,
editDescription: description,
}));
},
updateSteps() {
this.steps = Object.fromEntries(
this.stepsArray.map((step) => [
step.title,
step.description,
])
);
S.steps = JSON.stringify(this.steps);
},
addStep() {
if (
this.newStepTitle &&
this.newStepDescription
) {
this.stepsArray.push({
title: this.newStepTitle,
description: this.newStepDescription,
editing: false,
editTitle: this.newStepTitle,
editDescription: this.newStepDescription,
});
this.newStepTitle = "";
this.newStepDescription = "";
this.updateSteps();
}
},
debounceAddStep: debounce(function () {
this.addStep();
}, 300),
removeStep(index) {
this.stepsArray.splice(index, 1);
this.updateSteps();
},
debounceRemoveStep: debounce(function (index) {
this.removeStep(index);
}, 300),
moveStep(index, direction) {
if (
(direction === "up" && index > 0) ||
(direction === "down" &&
index < this.stepsArray.length - 1)
) {
const newIndex =
direction === "up" ? index - 1 : index + 1;
const temp = this.stepsArray[index];
this.stepsArray[index] =
this.stepsArray[newIndex];
this.stepsArray[newIndex] = temp;
this.updateSteps();
}
},
debounceMoveStep: debounce(function (
index,
direction
) {
this.moveStep(index, direction);
},
300),
editStep(index) {
this.stepsArray[index].editing = true;
},
saveEdit(index) {
const step = this.stepsArray[index];
step.title = step.editTitle;
step.description = step.editDescription;
step.editing = false;
this.updateSteps();
},
cancelEdit(index) {
const step = this.stepsArray[index];
step.editTitle = step.title;
step.editDescription = step.description;
step.editing = false;
},
};
}
</script>
</body>
</html>