Skip to content

Commit 7c20705

Browse files
authored
Add Stimulus framework (#6)
* Add Stimulus framework * Add ids to checkboxes for acessibility * Fixing lint issues * Updated stimulus stats according generate stats task * Fixing stats date
1 parent 938ab33 commit 7c20705

File tree

10 files changed

+120
-20
lines changed

10 files changed

+120
-20
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@astrojs/vercel": "^8.0.1",
3030
"@astrojs/vue": "^5.0.3",
3131
"@google/generative-ai": "^0.21.0",
32+
"@hotwired/stimulus": "^3.2.2",
3233
"@types/alpinejs": "^3.13.11",
3334
"@types/react": "^19.0.2",
3435
"@types/react-dom": "^19.0.2",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/assets/logos/stimulus.svg

Lines changed: 7 additions & 0 deletions
Loading
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<style>
2+
.stimulus-container { @apply px-3 py-2 }
3+
.stimulus-parent { @apply p-2 rounded-lg }
4+
.stimulus-children { @apply ml-8 rounded-lg }
5+
.stimulus-checkbox { @apply p-1 flex items-center space-x-3 hover:bg-slate-100 transition-colors }
6+
.stimulus-checkbox-input { @apply h-4 w-4 mt-0.5 text-blue-600 rounded border-slate-300 focus:ring-blue-500 }
7+
.stimulus-checkbox-label { @apply text-slate-700 cursor-pointer text-sm font-medium }
8+
</style>
9+
10+
<fieldset data-controller="checkbox-group" class="stimulus-container" aria-label="Checkbox group">
11+
<legend class="sr-only">Parent-child checkbox group</legend>
12+
13+
<div class="stimulus-parent">
14+
<div class="stimulus-checkbox">
15+
<input id="stimulus-parent" type="checkbox" data-checkbox-group-target="parent" data-action="checkbox-group#parentToggle" class="stimulus-checkbox-input" aria-controls="stimulus-children" />
16+
<label for="stimulus-parent" class="stimulus-checkbox-label">Parent</label>
17+
</div>
18+
</div>
19+
20+
<div id="stimulus-children" class="stimulus-children">
21+
{ [1, 2, 3].map((i) =>
22+
<div class="stimulus-checkbox">
23+
<input id={`stimulus-child-${i}`} type="checkbox" data-checkbox-group-target="child" data-action="checkbox-group#childToggle" class="stimulus-checkbox-input" />
24+
<label for={`stimulus-child-${i}`} class="stimulus-checkbox-label">Child {i}</label>
25+
</div> )}
26+
</div>
27+
</fieldset>
28+
29+
<script>
30+
import { Application, Controller } from "@hotwired/stimulus"
31+
32+
window.Stimulus = Application.start()
33+
34+
window.Stimulus.register("checkbox-group", class extends Controller {
35+
static targets = [ 'parent', 'child' ]
36+
37+
declare readonly parentTarget: HTMLInputElement
38+
declare readonly childTargets: HTMLInputElement[]
39+
40+
parentToggle() {
41+
const checked = this.parentTarget.checked
42+
43+
this.childTargets.forEach(child => child.checked = checked)
44+
}
45+
46+
childToggle() {
47+
const allChecked = this.childTargets.every(child => child.checked)
48+
const someChecked = this.childTargets.some(child => child.checked)
49+
50+
this.parentTarget.checked = allChecked || someChecked
51+
this.parentTarget.indeterminate = someChecked && !allChecked
52+
}
53+
})
54+
</script>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
import stimulusLogo from "../../assets/logos/stimulus.svg";
3+
import CodeBlock from "../shared/CodeBlock.astro";
4+
import FrameworkContainer from "../shared/FrameworkContainer.astro";
5+
import Stimulus from "./stimulus.astro";
6+
7+
const { default: code } = await import("./stimulus.astro?raw");
8+
---
9+
10+
<FrameworkContainer
11+
framework="stimulus"
12+
title="Stimulus"
13+
>
14+
<Stimulus />
15+
<CodeBlock code={code} displayName="Stimulus" lang="html" logo={stimulusLogo} />
16+
</FrameworkContainer>

src/config/frameworks.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export const FRAMEWORKS = {
2323
jquery: {
2424
displayName: "jQuery",
2525
},
26+
stimulus: {
27+
displayName: "Stimulus",
28+
clientFramework: null,
29+
},
2630
} as const;
2731

2832
export type FrameworkId = keyof typeof FRAMEWORKS;

src/data/framework-stats.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@
5757
"complexityScore": 45,
5858
"bundleSizeZScore": -0.3132794025932311,
5959
"complexityZScore": 0.447213595499958
60+
},
61+
"stimulus": {
62+
"bundleSize": 12.67,
63+
"complexityScore": 22,
64+
"bundleSizeZScore": 0,
65+
"complexityZScore": 0
6066
}
6167
}
6268
}

src/pages/index.astro

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import CssOnlyContainer from "../components/cssOnly/cssOnlyContainer.astro";
55
import HyperscriptContainer from "../components/hyperscript/hyperscriptContainer.astro";
66
import JQueryContainer from "../components/jquery/jqueryContainer.astro";
77
import ReactContainer from "../components/react/ReactContainer.astro";
8+
import StimulusContainer from "../components/stimulus/stimulusContainer.astro";
89
import SvelteContainer from "../components/svelte/SvelteContainer.astro";
910
import VanillajsContainer from "../components/vanillajs/vanillajsContainer.astro";
1011
import VueContainer from "../components/vue/vueContainer.astro";
@@ -20,6 +21,7 @@ const containerMap = {
2021
hyperscript: HyperscriptContainer,
2122
cssOnly: CssOnlyContainer,
2223
jquery: JQueryContainer,
24+
stimulus: StimulusContainer,
2325
} as const;
2426
2527
const frameworks = Object.entries(FRAMEWORKS).map(([id]) => ({
@@ -39,9 +41,9 @@ const frameworks = Object.entries(FRAMEWORKS).map(([id]) => ({
3941
</div>
4042

4143
<FrameworkControls client:only="react" />
42-
43-
<div
44-
id="framework-grid"
44+
45+
<div
46+
id="framework-grid"
4547
class="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-6xl mx-auto"
4648
>
4749
{frameworks.map(({ Component }) => (
@@ -52,12 +54,12 @@ const frameworks = Object.entries(FRAMEWORKS).map(([id]) => ({
5254
</Layout>
5355

5456
<script>
55-
import {
56-
type SortableEvent,
57-
type FrameworkSelectionEvent,
58-
type FrameworkSortEvent,
57+
import {
58+
type SortableEvent,
59+
type FrameworkSelectionEvent,
60+
type FrameworkSortEvent,
5961
type FrameworkId,
60-
isValidFramework
62+
isValidFramework
6163
} from "../config/frameworks";
6264
import Sortable from "sortablejs";
6365

@@ -95,7 +97,7 @@ const frameworks = Object.entries(FRAMEWORKS).map(([id]) => ({
9597
evt.item.classList.remove('dragging');
9698
document.body.style.cursor = '';
9799
saveFrameworkOrder();
98-
100+
99101
// Dispatch event to reset sort dropdown
100102
document.dispatchEvent(new CustomEvent("frameworkDragSort"));
101103
}
@@ -119,7 +121,7 @@ const frameworks = Object.entries(FRAMEWORKS).map(([id]) => ({
119121
try {
120122
const order = JSON.parse(savedOrder);
121123
const containers = Array.from(gridElement.children);
122-
124+
123125
for (const frameworkId of order) {
124126
const container = containers.find(
125127
c => c.getAttribute('data-framework') === frameworkId
@@ -149,7 +151,7 @@ const frameworks = Object.entries(FRAMEWORKS).map(([id]) => ({
149151
function hideFramework(container: HTMLElement) {
150152
container.style.transform = 'scale(0.95)';
151153
container.style.opacity = '0';
152-
154+
153155
container.addEventListener('transitionend', function hideContainer(e) {
154156
if (e.propertyName === 'opacity') {
155157
container.classList.add('hidden');
@@ -182,7 +184,7 @@ const frameworks = Object.entries(FRAMEWORKS).map(([id]) => ({
182184
if (grid) {
183185
sortableInstance = initializeSortable(grid);
184186
restoreFrameworkOrder(grid);
185-
187+
186188
// Add resize listener
187189
window.addEventListener('resize', handleResize);
188190

@@ -211,7 +213,7 @@ const frameworks = Object.entries(FRAMEWORKS).map(([id]) => ({
211213
if (!grid) return;
212214

213215
const containers = Array.from(grid.children);
214-
216+
215217
e.detail.order.forEach((frameworkId) => {
216218
const container = containers.find(
217219
c => c.getAttribute('data-framework') === frameworkId
@@ -235,7 +237,7 @@ const frameworks = Object.entries(FRAMEWORKS).map(([id]) => ({
235237
[data-framework] {
236238
cursor: grab;
237239
}
238-
240+
239241
[data-framework].dragging {
240242
cursor: grabbing;
241243
}

src/pages/test/[framework].astro

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import CssOnly from "../../components/cssOnly/cssOnly.astro";
44
import Hyperscript from "../../components/hyperscript/hyperscript.astro";
55
import JQuery from "../../components/jquery/jquery.astro";
66
import ReactNestedCheckboxes from "../../components/react/ReactNestedCheckboxes";
7+
import Stimulus from "../../components/stimulus/stimulusContainer.astro";
78
import SvelteNestedCheckboxes from "../../components/svelte/SvelteNestedCheckboxes.svelte";
89
import Vanillajs from "../../components/vanillajs/vanillajs.astro";
910
import VueNestedCheckboxes from "../../components/vue/VueNestedCheckboxes.vue";
@@ -28,7 +29,7 @@ const frameworkId = framework;
2829
<Layout title={`${FRAMEWORKS[frameworkId].displayName} Test Page`}>
2930
<script>
3031
// Signal when the page is ready
31-
32+
3233
window.addEventListener('load', () => {
3334
// Give a small delay for frameworks to initialize
3435
setTimeout(() => {
@@ -37,8 +38,8 @@ const frameworkId = framework;
3738
});
3839
</script>
3940
<div class="min-h-screen p-8">
40-
<div
41-
class="framework-container"
41+
<div
42+
class="framework-container"
4243
data-framework={framework}
4344
data-framework-ready={framework}
4445
>
@@ -50,6 +51,7 @@ const frameworkId = framework;
5051
{frameworkId === "vanillajs" && <Vanillajs />}
5152
{frameworkId === "hyperscript" && <Hyperscript />}
5253
{frameworkId === "cssOnly" && <CssOnly />}
54+
{frameworkId === "stimulus" && <Stimulus />}
5355
</div>
5456
</div>
55-
</Layout>
57+
</Layout>

src/types/global.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import type { Application } from "@hotwired/stimulus";
12
declare global {
23
interface Window {
34
frameworkReady: boolean;
5+
Stimulus: Application;
46
}
57
}
6-
7-
export {};

0 commit comments

Comments
 (0)