Skip to content

Commit d20ae81

Browse files
tdwhite0claude
andcommitted
add 25 custom blocks for CMS
New blocks include: - accordion, tabs, carousel, video, embed, modal, table - quote, cta, testimonials, banner, gallery, counter - timeline, faq, pricing, team, features, alert - social-share, breadcrumb, divider, newsletter, progress-bar Each block includes: - JavaScript functionality with accessibility support - CSS with multiple style variants - Responsive design - Component definitions, models, and filters for AEM authoring Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d0d483f commit d20ae81

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+6323
-0
lines changed

blocks/accordion/accordion.css

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.accordion details {
2+
border: 1px solid var(--border-color, #dadada);
3+
border-radius: 4px;
4+
margin-bottom: 8px;
5+
overflow: hidden;
6+
}
7+
8+
.accordion summary {
9+
padding: 16px;
10+
font-weight: 600;
11+
cursor: pointer;
12+
background-color: var(--background-color, #fff);
13+
list-style: none;
14+
display: flex;
15+
justify-content: space-between;
16+
align-items: center;
17+
}
18+
19+
.accordion summary::-webkit-details-marker {
20+
display: none;
21+
}
22+
23+
.accordion summary::after {
24+
content: '+';
25+
font-size: 1.5rem;
26+
font-weight: 400;
27+
transition: transform 0.2s ease;
28+
}
29+
30+
.accordion details[open] summary::after {
31+
content: '-';
32+
}
33+
34+
.accordion .accordion-content {
35+
padding: 16px;
36+
border-top: 1px solid var(--border-color, #dadada);
37+
background-color: var(--light-color, #f5f5f5);
38+
}
39+
40+
.accordion details[open] {
41+
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
42+
}

blocks/accordion/accordion.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export default function decorate(block) {
2+
const items = [...block.children];
3+
4+
items.forEach((item) => {
5+
const details = document.createElement('details');
6+
const summary = document.createElement('summary');
7+
8+
const summaryDiv = item.children[0];
9+
const contentDiv = item.children[1];
10+
11+
summary.textContent = summaryDiv?.textContent || 'Accordion Item';
12+
13+
const content = document.createElement('div');
14+
content.className = 'accordion-content';
15+
if (contentDiv) {
16+
content.innerHTML = contentDiv.innerHTML;
17+
}
18+
19+
details.append(summary);
20+
details.append(content);
21+
item.replaceWith(details);
22+
});
23+
24+
// Optional: single-open behavior
25+
block.addEventListener('toggle', (e) => {
26+
if (e.target.open && block.dataset.singleOpen !== undefined) {
27+
block.querySelectorAll('details[open]').forEach((openDetails) => {
28+
if (openDetails !== e.target) {
29+
openDetails.open = false;
30+
}
31+
});
32+
}
33+
}, true);
34+
}

blocks/alert/alert.css

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
.alert {
2+
border-radius: 8px;
3+
overflow: hidden;
4+
}
5+
6+
.alert .alert-wrapper {
7+
display: flex;
8+
align-items: flex-start;
9+
gap: 12px;
10+
padding: 16px 20px;
11+
background-color: var(--light-color, #f5f5f5);
12+
border-left: 4px solid var(--link-color, #035fe6);
13+
}
14+
15+
.alert .alert-icon {
16+
flex-shrink: 0;
17+
width: 24px;
18+
height: 24px;
19+
color: var(--link-color, #035fe6);
20+
}
21+
22+
.alert .alert-icon svg {
23+
width: 100%;
24+
height: 100%;
25+
}
26+
27+
.alert .alert-content {
28+
flex: 1;
29+
color: var(--text-color, #333);
30+
}
31+
32+
.alert .alert-content p:first-child {
33+
margin-top: 0;
34+
}
35+
36+
.alert .alert-content p:last-child {
37+
margin-bottom: 0;
38+
}
39+
40+
.alert .alert-content strong {
41+
display: block;
42+
margin-bottom: 4px;
43+
}
44+
45+
.alert .alert-close {
46+
flex-shrink: 0;
47+
background: none;
48+
border: none;
49+
font-size: 1.5rem;
50+
line-height: 1;
51+
cursor: pointer;
52+
color: inherit;
53+
opacity: 0.6;
54+
padding: 0;
55+
margin: -4px -4px 0 0;
56+
transition: opacity 0.2s ease;
57+
}
58+
59+
.alert .alert-close:hover {
60+
opacity: 1;
61+
}
62+
63+
/* Info type (default) */
64+
.alert .alert-wrapper[data-type="info"] {
65+
background-color: #e7f3ff;
66+
border-left-color: #035fe6;
67+
}
68+
69+
.alert .alert-wrapper[data-type="info"] .alert-icon {
70+
color: #035fe6;
71+
}
72+
73+
/* Success type */
74+
.alert.success .alert-wrapper,
75+
.alert .alert-wrapper[data-type="success"] {
76+
background-color: #d4edda;
77+
border-left-color: #28a745;
78+
}
79+
80+
.alert.success .alert-icon,
81+
.alert .alert-wrapper[data-type="success"] .alert-icon {
82+
color: #28a745;
83+
}
84+
85+
/* Warning type */
86+
.alert.warning .alert-wrapper,
87+
.alert .alert-wrapper[data-type="warning"] {
88+
background-color: #fff3cd;
89+
border-left-color: #ffc107;
90+
}
91+
92+
.alert.warning .alert-icon,
93+
.alert .alert-wrapper[data-type="warning"] .alert-icon {
94+
color: #856404;
95+
}
96+
97+
/* Error/Danger type */
98+
.alert.error .alert-wrapper,
99+
.alert.danger .alert-wrapper,
100+
.alert .alert-wrapper[data-type="error"] {
101+
background-color: #f8d7da;
102+
border-left-color: #dc3545;
103+
}
104+
105+
.alert.error .alert-icon,
106+
.alert.danger .alert-icon,
107+
.alert .alert-wrapper[data-type="error"] .alert-icon {
108+
color: #dc3545;
109+
}
110+
111+
/* Filled variant */
112+
.alert.filled .alert-wrapper[data-type="info"] {
113+
background-color: #035fe6;
114+
color: #fff;
115+
}
116+
117+
.alert.filled .alert-wrapper[data-type="info"] .alert-icon {
118+
color: #fff;
119+
}
120+
121+
.alert.filled .alert-wrapper[data-type="success"] {
122+
background-color: #28a745;
123+
color: #fff;
124+
}
125+
126+
.alert.filled .alert-wrapper[data-type="success"] .alert-icon {
127+
color: #fff;
128+
}
129+
130+
.alert.filled .alert-wrapper[data-type="warning"] {
131+
background-color: #ffc107;
132+
color: #333;
133+
}
134+
135+
.alert.filled .alert-wrapper[data-type="warning"] .alert-icon {
136+
color: #333;
137+
}
138+
139+
.alert.filled .alert-wrapper[data-type="error"] {
140+
background-color: #dc3545;
141+
color: #fff;
142+
}
143+
144+
.alert.filled .alert-wrapper[data-type="error"] .alert-icon {
145+
color: #fff;
146+
}
147+
148+
.alert.filled .alert-content {
149+
color: inherit;
150+
}
151+
152+
/* Outlined variant */
153+
.alert.outlined .alert-wrapper {
154+
background-color: transparent;
155+
border: 1px solid;
156+
border-left-width: 4px;
157+
}
158+
159+
.alert.outlined .alert-wrapper[data-type="info"] {
160+
border-color: #035fe6;
161+
}
162+
163+
.alert.outlined .alert-wrapper[data-type="success"] {
164+
border-color: #28a745;
165+
}
166+
167+
.alert.outlined .alert-wrapper[data-type="warning"] {
168+
border-color: #ffc107;
169+
}
170+
171+
.alert.outlined .alert-wrapper[data-type="error"] {
172+
border-color: #dc3545;
173+
}
174+
175+
/* Compact variant */
176+
.alert.compact .alert-wrapper {
177+
padding: 12px 16px;
178+
}
179+
180+
.alert.compact .alert-icon {
181+
width: 20px;
182+
height: 20px;
183+
}

blocks/alert/alert.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export default function decorate(block) {
2+
const contentDiv = block.children[0];
3+
4+
const wrapper = document.createElement('div');
5+
wrapper.className = 'alert-wrapper';
6+
7+
// Detect type from class or content
8+
let type = 'info';
9+
if (block.classList.contains('warning')) type = 'warning';
10+
else if (block.classList.contains('error') || block.classList.contains('danger')) type = 'error';
11+
else if (block.classList.contains('success')) type = 'success';
12+
else if (block.classList.contains('info')) type = 'info';
13+
14+
wrapper.dataset.type = type;
15+
16+
// Icon
17+
const icon = document.createElement('div');
18+
icon.className = 'alert-icon';
19+
switch (type) {
20+
case 'success':
21+
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22,4 12,14.01 9,11.01"/></svg>';
22+
break;
23+
case 'warning':
24+
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>';
25+
break;
26+
case 'error':
27+
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>';
28+
break;
29+
default:
30+
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>';
31+
}
32+
wrapper.append(icon);
33+
34+
// Content
35+
const content = document.createElement('div');
36+
content.className = 'alert-content';
37+
if (contentDiv) {
38+
content.innerHTML = contentDiv.innerHTML;
39+
}
40+
wrapper.append(content);
41+
42+
// Dismissible
43+
if (block.classList.contains('dismissible')) {
44+
const closeBtn = document.createElement('button');
45+
closeBtn.className = 'alert-close';
46+
closeBtn.setAttribute('aria-label', 'Dismiss alert');
47+
closeBtn.innerHTML = '&times;';
48+
closeBtn.addEventListener('click', () => {
49+
block.style.display = 'none';
50+
});
51+
wrapper.append(closeBtn);
52+
}
53+
54+
block.textContent = '';
55+
block.append(wrapper);
56+
}

0 commit comments

Comments
 (0)