Skip to content

Commit 47aafa8

Browse files
authored
Merge pull request #4 from wunderio/main
Focus Trap, Language Switcher
2 parents bf11058 + 6e6d999 commit 47aafa8

30 files changed

Lines changed: 775 additions & 232 deletions

README.md

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,63 @@
1-
# wudo
2-
Drupal 11 base theme (SDC).
1+
# WUDO
2+
Drupal 11 base theme (SDC, Web Components, Lit).
33

44
Wudo, derived from the Wunder and the Japanese Dō (Path).
55

6+
![Drupal](https://img.shields.io/badge/Drupal-11-0678BE?style=flat-square&logo=drupal&logoColor=white)
7+
![Storybook](https://img.shields.io/badge/Storybook-10-FF4785?style=flat-square&logo=storybook&logoColor=white)
8+
![Vite](https://img.shields.io/badge/Vite-Build_Tool-646CFF?style=flat-square&logo=vite&logoColor=white)
9+
![Web Components](https://img.shields.io/badge/Web_Components-Native-orange?style=flat-square&logo=webcomponents&logoColor=white)
10+
![Lit](https://img.shields.io/badge/Lit-3.0-blueviolet?style=flat-square&logo=lit&logoColor=white)
11+
12+
613
## Why This Stack?
714
* **Single Directory Components (SDC)** keep markup, styles, metadata, and logic together, making components clear, reusable, and Drupal-native.
815
* **Vite** is chosen for speed, simplicity, and modern ESM workflows.
16+
* **Lit** provides a lightweight, reactive layer for Web Components. It ensures that complex UI logic remains fast and encapsulated, allowing for high interactivity with minimal overhead
917
* **Storybook** is isolated by design to enforce clean, native, CMS-agnostic components.
1018

11-
This combination ensures:
12-
* fast development
13-
* predictable behavior
14-
* long-term maintainability
15-
* clear component ownership and structure
19+
## Component Inventory
20+
21+
### Atoms
22+
23+
| Component | Status | SDC | Web Cmp | Lit |
24+
|:------------------------------------------------------|:----------------|----------|----------|-----|
25+
| [Button](./components/01-atoms/button) | stable | ✅ | | |
26+
| [Link](./components/01-atoms/link) | experimental | ✅ | | |
27+
| [Paragraph](./components/01-atoms/paragraph) | stable | ✅ | | |
28+
| [Icon](./components/01-atoms/icon) | stable | ✅ | | |
29+
| [Theme Toggler](./components/01-atoms/theme-toggler) | stable | ✅ | ✅ | |
30+
| [Quantity Input](./components/01-atoms/form/quantity) | stable | ✅ | ✅ | |
31+
| [Back To Top](./components/01-atoms/back-to-top) | experimental | ✅ | ✅ | |
32+
33+
### Molecules
34+
| Component | Version | Status | SDC | Web Cmp | Lit |
35+
|:-----------------------------------------------------------------|---------|:--------------|----------|----------|-----|
36+
| [Accordion](./components/02-molecules/accordion) | | stable | ✅ | | |
37+
| [Article card](./components/02-molecules/article-card) | | experimental | ✅ | | |
38+
| [Tabs](./components/02-molecules/tabs) | | experimental | ✅ | | |
39+
| [Pagination](./components/02-molecules/pagination) | | stable | ✅ | | |
40+
| [Countdown](./components/02-molecules/countdown) | | experimental | ✅ | ✅ | |
41+
| [Stat Card](./components/02-molecules/stat-card) | | experimental | ✅ | ✅ | |
42+
| [Toast Messages](./components/02-molecules/toast-messages) | | experimental | ✅ | ✅ | |
43+
| [Attribute List](./components/02-molecules/attribute-list) | | stable | ✅ | | |
44+
| [Language switcher](./components/02-molecules/language-switcher) | v1.0.0 | experimental | ✅ | ✅ | |
45+
46+
### Organisms
47+
| Component | Version | Status | SDC | Web Cmp | Lit |
48+
|:-----------------------------------------------|---------|:-------------|-----------|----------|-----------|
49+
| [Header](./components/03-organisms/header) | | stable | ✅ | | |
50+
| [Drawer](./components/03-organisms/drawer) | v1.0.1 | experimental | ✅ | ✅ | |
51+
| [Carousel](./components/03-organisms/carousel) | | experimental | ✅ | ✅ | |
52+
| [Favorite](./components/03-organisms/favorite) | | experimental | ✅ | ✅ | ✅ |
53+
54+
### Utilities
55+
| Component | Version | Status | SDC | Web Cmp | Description |
56+
|:------------------------------------------------------------------|----------|:-------------|-----------|----------|:--------------------------------------------------------|
57+
| [Link Manager](./components/00-base/01-primitives/link-manager) | | experimental | ✅ | ✅ | automatically manages external links |
58+
| [Sticky Header](./components/00-base/01-primitives/sticky-header) | | experimental | ✅ | ✅ | Sticky header |
59+
| [Reveal](./components/00-base/01-primitives/reveal) | | experimental | ✅ | ✅ | provides a "reveal on scroll" animation for its content |
60+
| [Focus Trap](./components/00-base/01-primitives/focus-trap) | v1.0.0 | stable | | | JS only component used by Drawer, Menu toggle |
1661

1762
## Quick Start
1863

@@ -22,17 +67,25 @@ Install all dependencies
2267
npm install
2368
```
2469

25-
Starts the Vite development server
70+
Develop common Drupal styles
2671

2772
```bash
2873
npm run dev
2974
```
30-
75+
Develop SDC components
76+
```bash
77+
npm run dev:sdc
78+
```
3179
Build SDC components and main style.css
3280

3381
```bash
3482
npm run build
3583
```
84+
Run Storybook
85+
```bash
86+
npm run storybook
87+
```
88+
3689
## Theme Renaming
3790
This theme includes a migration script to safely change the theme's machine name. This is particularly useful when using this repository as a starter kit.
3891
### What the script does:
@@ -57,60 +110,18 @@ Run the script:
57110
```
58111
Enter your new theme machine name (e.g., `my_awesome_theme`) when prompted.
59112

60-
## Theme Management
113+
## Theme Light / Dark modes
61114

62115
This project implements a flexible theme system with native Dark Mode support.
63116

64117
### 1. Native System Support (Default)
65118
The theme is **Auto-first** by design. Even without the toggler component, the site automatically inherits the user's OS preference via `prefers-color-scheme`. Dark mode CSS variables are active by default to ensure a seamless experience from the first visit.
66119

67120
### 2. Manual Theme Toggler
68-
The `<wudo-theme-toggler>` Web Component allows users to manually override system settings.
121+
The [Theme Toggler](./components/01-atoms/theme-toggler) Web Component allows users to manually override system settings.
69122
* **Tri-state Logic:** Cycles through **Auto** (System), **Dark**, and **Light** modes.
70123
* **Zero Flicker:** Applies `data-theme` to the `<html>` element instantly to prevent Layout Shift.
71124
* **Fully Localized:** All labels and ARIA attributes are passed from Drupal/Twig, making the component 100% translatable.
72-
## Component Inventory
73-
74-
### Atoms
75-
Fundamental building blocks that cannot be broken down further.
76-
77-
| Component | Status | Description |
78-
|:------------------------------------------------------|:----------------|:----------------------------------------------------------------------|
79-
| [Button](./components/01-atoms/button) | stable | **SDC** - button |
80-
| [Link](./components/01-atoms/link) | experimental | **SDC** - link |
81-
| [Paragraph](./components/01-atoms/paragraph) | stable | **SDC** - paragraph |
82-
| [Icon](./components/01-atoms/icon) | stable | **SDC** - SVG icon component |
83-
| [Theme Toggler](./components/01-atoms/theme-toggler) | stable | **SDC + Web Component** - manages Light/Dark mode for the entire site |
84-
| [Quantity Input](./components/01-atoms/form/quantity) | stable | **SDC + Web Component** - numerical input |
85-
| [Back To Top](./components/01-atoms/back-to-top) | experimental | **SDC + Web Component** |
86-
87-
### Molecules
88-
| Component | Status | Description |
89-
|:-----------------------------------------------------------|:--------------|:----------------------------------------------------|
90-
| [Accordion](./components/02-molecules/accordion) | stable | **SDC** - collapsible content sections |
91-
| [Article card](./components/02-molecules/article-card) | experimental | **SDC** - publication card with image |
92-
| [Tabs](./components/02-molecules/tabs) | experimental | **SDC** - accessible tabs |
93-
| [Pagination](./components/02-molecules/pagination) | stable | **SDC** - pager component |
94-
| [Countdown](./components/02-molecules/countdown) | experimental | **SDC + Web Component** - countdown |
95-
| [Stat Card](./components/02-molecules/stat-card) | experimental | **SDC + Web Component** - card with animated number |
96-
| [Toast Messages](./components/02-molecules/toast-messages) | experimental | **SDC + Web Component** - notifications |
97-
| [Attribute List](./components/02-molecules/attribute-list) | stable | **SDC** - definition list |
98-
99-
### Organisms
100-
| Component | Status | Description |
101-
|:-----------------------------------------------|:-------------|:------------------------------------------------------------------------------------------------------------------------------------|
102-
| [Header](./components/03-organisms/header) | stable | **SDC** - header component |
103-
| [Drawer](./components/03-organisms/drawer) | experimental | **SDC + Web Component** - container component that functions as both off-canvas sidebars (menus, carts) and centered modal dialogs. |
104-
| [Carousel](./components/03-organisms/carousel) | experimental | **SDC + Web Component** - a lightweight and accessible content slider |
105-
| [Favorite](./components/03-organisms/favorite) | experimental | **SDC + Web Component + Lit** - Save to list (watchlist, favorites, etc.) |
106-
107-
### Utilities
108-
| Component | Status | Description |
109-
|:------------------------------------------------------------------|:-------------|:--------------------------------------------------------------------------------------------|
110-
| [Link Manager](./components/00-base/01-primitives/link-manager) | experimental | **SDC + Web Component** - automatically manages external links |
111-
| [Sticky Header](./components/00-base/01-primitives/sticky-header) | experimental | **SDC + Web Component** - adds "Smart Sticky" functionality to any header or navigation bar |
112-
| [Reveal](./components/00-base/01-primitives/reveal) | experimental | **SDC + Web Component** - provides a "reveal on scroll" animation for its content |
113-
| [Focus Trap](./components/00-base/01-primitives/focus-trap) | stable | JS only component used by Drawer, Menu toggle |
114125

115126

116127
## Documentation

assets/icons/globe.svg

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@use "02-breakpoints" as *;
2+
3+
.u-mobile-only {
4+
display: block;
5+
6+
@include mobile {
7+
display: none;
8+
}
9+
}
10+
11+
.u-desktop-only {
12+
display: none;
13+
14+
@include mobile {
15+
display: block;
16+
}
17+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Focus Management System
2+
A robust, stack-based focus management system for Drupal themes. This system consists of a `FocusTrapManager` to handle multiple UI layers (like nested menus or modals) and a dynamic `FocusTrap` class to ensure keyboard accessibility (A11y).
3+
4+
## Key Features
5+
* **Stack-Based Logic:** Handles multiple overlapping components (e.g., a Mega Menu inside a Mobile Menu) without focus conflicts.
6+
* **Dynamic Discovery:** Recalculates focusable boundaries on every `Tab` press, making it perfect for menus with toggleable sub-sections.
7+
* **External Element Support:** Allows including elements outside the main container (like a toggle button) into the focus rotation.
8+
* **A11y Optimized:** Manages `aria-expanded` states, handles the `Escape` key via custom events, and prevents background scrolling.
9+
10+
### Used By
11+
* **Drawer:** For trapping focus within the drawer when it's open.
12+
* **Menu Toggle:** To manage focus within the menu and its toggle button, especially when the menu has nested items.
13+
14+
## FocusTrapManager (The Orchestrator)
15+
The `FocusTrapManager` lives on the global window object and tracks all active traps in a stack.
16+
17+
Usage:
18+
```javascript
19+
// Activating a trap
20+
const trap = window.focusTrapManager.activate(containerElement, {
21+
escapeCloses: true,
22+
additionalElements: [toggleButton]
23+
});
24+
25+
// Deactivating a trap
26+
window.focusTrapManager.deactivate(trap);
27+
```
28+
## FocusTrap Class (The Engine)
29+
The `FocusTrap` class handles the actual keyboard interception.
30+
31+
Configuration Options
32+
33+
| Option | Type | Default | Description |
34+
|:---------------------|:--------------|:--------|:-------------------------------------------------------------------|
35+
| `escapeCloses` | boolean | true | Whether the `Escape` key should close the trap. |
36+
| `additionalElements` | HTMLElement[] | [] | Extra elements to include in the focus loop (e.g., toggle buttons).|
37+
38+
Custom Events
39+
When the `Escape` key is pressed, the trap dispatches a custom event on the container:
40+
```javascript
41+
container.addEventListener('focusTrap:escape', () => {
42+
// Logic to close your component
43+
});
44+
```
45+
46+
## Implementation Examples
47+
48+
```javascript
49+
const toggle = wrapper.querySelector('[data-menu-toggle]');
50+
const menu = wrapper.querySelector('[data-menu]');
51+
let trap = null;
52+
53+
// Open the menu and activate the focus trap
54+
const open = () => {
55+
// Basic logic here (e.g., adding classes, preventing scroll, etc.)
56+
57+
// Activate the focus trap with the menu as the container and the toggle button as an additional element
58+
trap = window.focusTrapManager.activate(menu, {
59+
additionalElements: [toggle]
60+
});
61+
62+
// Pro-Tip: Move focus to the first focusable element in the menu after it opens
63+
requestAnimationFrame(() => {
64+
const firstLink = menu.querySelector('a, button');
65+
if (firstLink) firstLink.focus({ preventScroll: true });
66+
});
67+
68+
// Call close() when the Escape key is pressed within the trap
69+
menu.addEventListener('focusTrap:escape', close, { once: true });
70+
};
71+
72+
// Closing the menu and deactivating the focus trap
73+
const close = () => {
74+
// Basic logic here (e.g., removing classes, restoring scroll, etc.)
75+
76+
if (trap) {
77+
window.focusTrapManager.deactivate(trap);
78+
trap = null;
79+
}
80+
};
81+
82+
// rest JS logic (e.g., event listeners)
83+
toggle.addEventListener('click', () => {
84+
const isExpanded = toggle.getAttribute('aria-expanded') === 'true';
85+
// Note: open() and close() should also update aria-expanded on the toggle
86+
isExpanded ? close() : open();
87+
});
88+
```
89+
Pause a video when a trap is active:
90+
```javascript
91+
if (window.focusTrapManager.activeTrap) {
92+
video.pause();
93+
}
94+
```

0 commit comments

Comments
 (0)