so-web-components 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,437 @@
1
+ const DEFAULT_TOP_NAV = [
2
+ { label: "Regierung", href: "#" },
3
+ { label: "Gerichte", href: "#" },
4
+ { label: "Parlament", href: "#" },
5
+ { label: "Karriere", href: "#" },
6
+ { label: "my.so.ch", href: "#" }
7
+ ];
8
+ const DEFAULT_SECTION_NAV = [
9
+ { label: "Services", href: "#" },
10
+ { label: "Verwaltung", href: "#" }
11
+ ];
12
+ function safeParseNav(json, fallback) {
13
+ if (!json)
14
+ return fallback;
15
+ try {
16
+ const v = JSON.parse(json);
17
+ if (!Array.isArray(v))
18
+ return fallback;
19
+ const items = [];
20
+ for (const it of v) {
21
+ if (!it || typeof it !== "object")
22
+ continue;
23
+ const label = it["label"];
24
+ const href = it["href"];
25
+ if (typeof label === "string" && typeof href === "string") {
26
+ items.push({ label, href });
27
+ }
28
+ }
29
+ return items.length ? items : fallback;
30
+ }
31
+ catch {
32
+ return fallback;
33
+ }
34
+ }
35
+ function el(tag, cls) {
36
+ const n = document.createElement(tag);
37
+ if (cls)
38
+ n.className = cls;
39
+ return n;
40
+ }
41
+ function buildNavList(items, options = {}) {
42
+ const ul = el("ul", options.className);
43
+ for (const item of items) {
44
+ const li = el("li");
45
+ const a = el("a");
46
+ a.href = item.href;
47
+ const handled = options.setLinkContent?.(a, item);
48
+ if (!handled) {
49
+ a.textContent = item.label;
50
+ }
51
+ options.setLinkAttributes?.(a, item);
52
+ li.appendChild(a);
53
+ ul.appendChild(li);
54
+ }
55
+ return ul;
56
+ }
57
+ const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" id="logo" viewBox="0 0 171.086 15.742" aria-hidden="true" focusable="false">
58
+ <g>
59
+ <path d="m20.424.104h.877l-.595,2.634.016.017L23.658.104h1.156l-3.296,2.951,2.43,3.036h-1.105l-2.242-2.864-.613,2.864h-.887L20.424.104Z" fill="#1d1d1b"></path>
60
+ <path d="m31.676,3.878h-2.428l1.852-2.848.576,2.848Zm-4.827,2.213h.972l.981-1.509h3.006l.324,1.509h.904L31.685.104h-.867s-3.969,5.987-3.969,5.987Z" fill="#1d1d1b"></path>
61
+ <path d="m37.714.104h1.157l1.726,5.017h.018L41.701.104h.833l-1.332,5.987h-1.131l-1.726-4.975h-.016l-1.042,4.975h-.843L37.714.104Z" fill="#1d1d1b"></path>
62
+ <path d="m50.965.858h-1.903l-1.147,5.233h-.886l1.149-5.233h-1.902l.167-.754h4.687s-.165.754-.165.754Z" fill="#1d1d1b"></path>
63
+ <path d="m54.55,3.681c0-1.527.998-2.925,2.496-2.925,1.052,0,1.577.644,1.577,1.793,0,1.305-.891,2.892-2.401,2.892-1.105,0-1.671-.695-1.671-1.76h0Zm-.93-.025c0,1.654.884,2.538,2.567,2.538,2.218,0,3.367-1.87,3.367-3.765,0-1.527-.92-2.429-2.481-2.429-2.242,0-3.453,1.956-3.453,3.656Z" fill="#1d1d1b"></path>
64
+ <path d="m64.057.104h1.157l1.726,5.017h.018l1.087-5.017h.832l-1.332,5.987h-1.13l-1.726-4.975h-.018l-1.043,4.975h-.842s1.271-5.987,1.271-5.987Z" fill="#1d1d1b"></path>
65
+ <path d="m69.661,12.986c.847.367,1.765.551,2.684.551.532,0,1.855-.054,1.855-.846,0-1.359-3.952-.661-3.952-3.824,0-2.242,2.611-3.142,4.834-3.142.828,0,1.987.182,3.181.57l-.699,2.168c-.846-.348-1.654-.533-2.648-.533-.734,0-1.543.166-1.543.827,0,1.065,4.136.993,4.136,3.4,0,2.372-2.005,3.548-4.485,3.584-1.453.019-2.997-.201-4.027-.495l.663-2.261h0Z" fill="#1d1d1b"></path>
66
+ <path d="m87.97,9.971c0,2.17-.974,3.457-2.519,3.457-1.12.018-1.8-.847-1.8-2.133,0-1.526.992-3.254,2.592-3.254,1.288,0,1.728.939,1.728,1.93h0Zm3.419.037c0-2.408-1.728-4.282-4.982-4.282-3.75,0-6.176,2.278-6.176,5.642,0,2.407,1.434,4.374,4.982,4.374,3.583,0,6.176-1.745,6.176-5.735h0Z" fill="#1d1d1b"></path>
67
+ <path d="m95.708,1.737h3.346l-2.868,13.786h-3.345l2.868-13.786h0Z" fill="#1d1d1b"></path>
68
+ <path d="m107.455,9.971c0,2.17-.976,3.457-2.519,3.457-1.12.018-1.802-.847-1.802-2.133,0-1.526.994-3.254,2.593-3.254,1.286,0,1.728.939,1.728,1.93Zm3.418.037c0-2.408-1.728-4.282-4.98-4.282-3.751,0-6.178,2.278-6.178,5.642,0,2.407,1.434,4.374,4.983,4.374,3.583,0,6.174-1.745,6.174-5.735h.001Z" fill="#1d1d1b"></path>
69
+ <path d="m113.372,5.946h1.894l.458-2.059,3.495-.938-.663,2.997h2.28l-.405,2.317h-2.408l-.571,2.665c-.146.716-.273,1.194-.273,1.653,0,.644.421,1.066,1.083,1.066.349,0,.717-.147,1.066-.276l-.366,2.187c-.699.111-1.398.183-2.059.183-1.691,0-3.034-.827-3.034-2.83,0-.478.072-.956.294-1.966l.57-2.684h-1.765l.404-2.317v.002Z" fill="#1d1d1b"></path>
70
+ <path d="m125.247,1.737h3.346l-1.177,5.606h.037c.68-.899,1.783-1.617,3.328-1.617,1.929,0,2.975,1.434,2.975,3.308,0,.643-.053,1.01-.255,2.022l-.882,4.467h-3.419l.918-4.485c.093-.479.222-.955.222-1.453,0-.57-.366-1.14-1.286-1.103-1.397,0-2.225,1.215-2.464,2.628l-.936,4.412h-3.31l2.905-13.786h-.002Z" fill="#1d1d1b"></path>
71
+ <path d="m146.496,13.023c-.165.828-.312,1.636-.405,2.5h-3.069l.258-1.563h-.037c-.956,1.066-1.948,1.782-3.696,1.782-1.929,0-2.975-1.433-2.975-3.308,0-.644.053-1.011.257-2.021l.882-4.468h3.418l-.918,4.485c-.093.478-.222.955-.222,1.452,0,.571.368,1.14,1.286,1.104,1.398,0,2.226-1.213,2.464-2.628l.937-4.412h3.308l-1.489,7.077h.001Z" fill="#1d1d1b"></path>
72
+ <path d="m154.952,5.946l-.368,1.967h.037c.534-1.121,1.599-2.186,2.959-2.186.44,0,.956.054,1.397.238l-.754,2.923c-.423-.333-.9-.405-1.525-.405-1.397,0-2.223,1.215-2.463,2.628l-.937,4.412h-3.31l1.489-7.078c.167-.827.315-1.635.405-2.499h3.07,0Z" fill="#1d1d1b"></path>
73
+ <path d="m161.161,8.445c.166-.827.312-1.635.405-2.499h3.069l-.257,1.563h.035c.956-1.065,1.95-1.782,3.696-1.782,1.93,0,2.977,1.434,2.977,3.308,0,.643-.055,1.01-.257,2.022l-.882,4.467h-3.418l.918-4.485c.094-.479.222-.955.222-1.453,0-.57-.368-1.14-1.288-1.103-1.397,0-2.225,1.215-2.463,2.628l-.939,4.412h-3.307s1.489-7.078,1.489-7.078Z" fill="#1d1d1b"></path>
74
+ </g>
75
+ <g>
76
+ <path d="m2.364.109h13.713s-1.474,6.483-2.234,9.833c-.771,3.393-4.621,5.774-8.159,5.774-3.496,0-6.321-2.402-5.559-5.752C.886,6.613,2.364.109,2.364.109Z" fill="#fff"></path>
77
+ <path d="m4.602,15.073l-.88.375c.613.174,1.274.268,1.961.268,3.017,0,6.261-1.732,7.629-4.347l-.858.362c-1.473,2.159-4.339,3.436-6.771,3.436-.37,0-.73-.036-1.081-.093h0Z" fill="#1d1d1b"></path>
78
+ <path d="m2.364.109s-.682,2.998-1.357,5.972h13.713c.676-2.972,1.357-5.972,1.357-5.972,0,0-13.713,0-13.713,0Z" fill="#e01f26"></path>
79
+ </g>
80
+ </svg>`;
81
+ export class SoHeader extends HTMLElement {
82
+ constructor() {
83
+ super();
84
+ this.topNav = DEFAULT_TOP_NAV;
85
+ this.sectionNav = DEFAULT_SECTION_NAV;
86
+ this.activeSection = "Services";
87
+ this.logoHref = "/";
88
+ this.siteName = "Kanton Solothurn";
89
+ this.root = this.attachShadow({ mode: "open" });
90
+ this.updateFromAttributes();
91
+ this.render();
92
+ this.bindEvents();
93
+ }
94
+ attributeChangedCallback() {
95
+ this.updateFromAttributes();
96
+ this.render();
97
+ }
98
+ updateFromAttributes() {
99
+ this.topNav = safeParseNav(this.getAttribute("top-nav"), DEFAULT_TOP_NAV);
100
+ this.sectionNav = safeParseNav(this.getAttribute("section-nav"), DEFAULT_SECTION_NAV);
101
+ this.activeSection = this.getAttribute("active-section") ?? this.activeSection;
102
+ this.logoHref = this.getAttribute("logo-href") ?? this.logoHref;
103
+ this.siteName = this.getAttribute("site-name") ?? this.siteName;
104
+ }
105
+ bindEvents() {
106
+ this.root.addEventListener("click", (e) => {
107
+ const t = e.target;
108
+ const btn = t?.closest("button[data-action]");
109
+ if (btn?.dataset.action === "toggle-menu") {
110
+ this.toggleMobileMenu();
111
+ }
112
+ const a = t?.closest("a[data-section]");
113
+ if (a) {
114
+ const label = a.dataset.section ?? "";
115
+ this.dispatchEvent(new CustomEvent("so-section-select", {
116
+ detail: { label, href: a.getAttribute("href") ?? "" },
117
+ bubbles: true,
118
+ composed: true
119
+ }));
120
+ }
121
+ });
122
+ }
123
+ toggleMobileMenu() {
124
+ const panel = this.root.querySelector(".mobile-panel-inner");
125
+ const btn = this.root.querySelector('button[data-action="toggle-menu"]');
126
+ if (!panel || !btn)
127
+ return;
128
+ const open = panel.getAttribute("data-open") === "true";
129
+ const nextOpen = !open;
130
+ panel.setAttribute("data-open", nextOpen ? "true" : "false");
131
+ btn.setAttribute("aria-expanded", nextOpen ? "true" : "false");
132
+ btn.setAttribute("data-open", nextOpen ? "true" : "false");
133
+ btn.innerHTML = nextOpen ? closeIcon() : burgerIcon();
134
+ }
135
+ render() {
136
+ this.root.innerHTML = "";
137
+ const styleEl = document.createElement("style");
138
+ styleEl.textContent = this.styles();
139
+ this.root.appendChild(styleEl);
140
+ const header = el("header", "so-header");
141
+ header.setAttribute("role", "banner");
142
+ // Row 1: full-width, internal container for gutters
143
+ const top = el("div", "topbar");
144
+ const topInner = el("div", "topbar-inner so-header-container");
145
+ //topInner.style.setProperty("--so-container-size", "var(--so-container-size-full)");
146
+ const left = el("div", "left");
147
+ const logoLink = el("a", "logo");
148
+ logoLink.href = this.logoHref;
149
+ logoLink.setAttribute("aria-label", this.siteName);
150
+ logoLink.title = this.siteName;
151
+ logoLink.innerHTML = LOGO_SVG;
152
+ left.appendChild(logoLink);
153
+ const right = el("div", "right");
154
+ const nav = el("nav", "topnav");
155
+ nav.setAttribute("aria-label", "Hauptnavigation");
156
+ const ul = buildNavList(this.topNav, {
157
+ setLinkContent: (a, item) => {
158
+ if (item.label === "my.so.ch") {
159
+ const label = el("span");
160
+ label.textContent = item.label;
161
+ a.append(label);
162
+ a.insertAdjacentHTML("beforeend", ` <svg class="icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><title>my.so.ch</title><path d="M20.39 18.71c1.48-1.84 2.36-4.17 2.36-6.71 0-5.93-4.82-10.75-10.75-10.75S1.25 6.07 1.25 12 6.07 22.75 12 22.75c3.39 0 6.41-1.58 8.38-4.04ZM12 2.75c5.1 0 9.25 4.15 9.25 9.25 0 1.93-.6 3.72-1.61 5.21-1.08-.92-3.49-2.46-7.64-2.46s-6.56 1.54-7.64 2.46A9.156 9.156 0 0 1 2.75 12c0-5.1 4.15-9.25 9.25-9.25Zm0 18.5c-2.63 0-5.01-1.11-6.69-2.88.84-.74 2.93-2.12 6.69-2.12s5.85 1.38 6.69 2.12C17 20.14 14.63 21.25 12 21.25Z"/><path d="M12 12.75c2.07 0 3.75-1.68 3.75-3.75S14.07 5.25 12 5.25 8.25 6.93 8.25 9s1.68 3.75 3.75 3.75Zm0-6c1.24 0 2.25 1.01 2.25 2.25s-1.01 2.25-2.25 2.25S9.75 10.24 9.75 9 10.76 6.75 12 6.75Z"/></svg>`);
163
+ return true;
164
+ }
165
+ return false;
166
+ }
167
+ });
168
+ nav.appendChild(ul);
169
+ const actions = el("div", "actions");
170
+ const btnMenu = el("button", "iconbtn menubtn");
171
+ btnMenu.type = "button";
172
+ btnMenu.dataset.action = "toggle-menu";
173
+ btnMenu.setAttribute("aria-label", "Menü");
174
+ btnMenu.setAttribute("aria-expanded", "false");
175
+ btnMenu.setAttribute("data-open", "false");
176
+ btnMenu.innerHTML = burgerIcon();
177
+ actions.append(btnMenu);
178
+ right.append(nav, actions);
179
+ topInner.append(left, right);
180
+ top.appendChild(topInner);
181
+ // Row 2: section nav
182
+ const second = el("div", "secondbar");
183
+ const secondInner = el("div", "secondbar-inner so-header-container");
184
+ //secondInner.style.setProperty("--so-container-size", "var(--so-container-size-full)");
185
+ const sectionNav = el("nav", "sectionnav");
186
+ sectionNav.setAttribute("aria-label", "Bereiche");
187
+ const sul = buildNavList(this.sectionNav, {
188
+ setLinkAttributes: (a, item) => {
189
+ a.dataset.section = item.label;
190
+ if (item.label === this.activeSection)
191
+ a.setAttribute("aria-current", "page");
192
+ }
193
+ });
194
+ sectionNav.appendChild(sul);
195
+ secondInner.appendChild(sectionNav);
196
+ second.appendChild(secondInner);
197
+ // Mobile panel (drawer-ish)
198
+ const mobilePanelWrap = el("div", "mobile-panel");
199
+ const mobilePanel = el("div", "mobile-panel-inner so-container");
200
+ //mobilePanel.style.setProperty("--so-container-size", "var(--so-container-size-full)");
201
+ mobilePanel.setAttribute("data-open", "false");
202
+ const mpTop = el("div", "mobile-group");
203
+ mpTop.appendChild(el("div", "mobile-title")).textContent = "Navigation";
204
+ const mpUl = buildNavList(this.topNav, { className: "mobile-list" });
205
+ mpTop.appendChild(mpUl);
206
+ const mpSections = el("div", "mobile-group");
207
+ mpSections.appendChild(el("div", "mobile-title")).textContent = "Bereiche";
208
+ const msUl = buildNavList(this.sectionNav, { className: "mobile-list" });
209
+ mpSections.appendChild(msUl);
210
+ mobilePanel.append(mpTop, mpSections);
211
+ mobilePanelWrap.appendChild(mobilePanel);
212
+ header.append(top, second, mobilePanelWrap);
213
+ this.root.appendChild(header);
214
+ }
215
+ styles() {
216
+ return (`
217
+ :host{
218
+ display: block;
219
+ background: var(--so-bg, #fff);
220
+ color: var(--so-fg, rgb(47, 72, 88));
221
+ font-family: var(--so-font-family, Frutiger, sans-serif);
222
+ font-size: var(--so-font-size, 16px);
223
+ line-height: var(--so-line-height, 1.5);
224
+ }
225
+
226
+ .so-header{
227
+ background: var(--so-bg, #fff);
228
+ color: var(--so-fg, rgb(47, 72, 88));
229
+ border-bottom: 1px solid var(--so-border-color);
230
+ }
231
+
232
+ .so-container{
233
+ width: 100%;
234
+ max-width: var(--so-container-size, var(--so-container-size-full, 120rem));
235
+ margin-inline: auto;
236
+ padding-inline: var(--so-container-padding, var(--so-space-6, 1.5rem));
237
+ box-sizing: border-box;
238
+ }
239
+
240
+ .so-header-container{
241
+ width: 100%;
242
+ margin-inline: auto;
243
+ padding-top: var(--so-container-padding, var(--so-space-6, 1.5rem));
244
+ padding-inline: var(--so-container-padding, var(--so-space-6, 1.5rem));
245
+ box-sizing: border-box;
246
+ }
247
+
248
+ .topbar-inner{
249
+ display: flex;
250
+ justify-content: space-between;
251
+ align-items: center;
252
+ gap: 1.25rem;
253
+ }
254
+
255
+ .logo{
256
+ display: inline-flex;
257
+ align-items: center;
258
+ text-decoration: none;
259
+ }
260
+ .logo svg#logo{
261
+ width: 10.125rem;
262
+ height: 1rem;
263
+ /*width: 10.7rem;*/ /* close to screenshot */
264
+ height: auto;
265
+ display: block;
266
+ }
267
+
268
+ /* Top links */
269
+ .right{ display: flex; align-items: center; gap: 1rem; }
270
+ .topnav ul{
271
+ list-style: none;
272
+ display: flex;
273
+ gap: 2.5rem;
274
+ margin: 0;
275
+ padding: 0;
276
+ align-items: center;
277
+ white-space: nowrap;
278
+ }
279
+ .topnav a{
280
+ text-decoration: none;
281
+ font-weight: 900;
282
+ color: var(--so-fg, rgb(47, 72, 88));
283
+ display: inline-flex;
284
+ align-items: center;
285
+ gap: 0.4rem;
286
+ line-height: 1;
287
+ }
288
+ .topnav a span{
289
+ line-height: 1;
290
+ }
291
+ .topnav .icon{
292
+ width: 1.5rem;
293
+ height: 1.5rem;
294
+ fill: currentColor;
295
+ display: block;
296
+ align-self: center;
297
+ transform: translateY(-2px);
298
+ }
299
+ .topnav a:hover{ color: rgb(204, 0, 0); }
300
+
301
+ .actions{ display: flex; align-items: center; }
302
+ .iconbtn{
303
+ border: 1px solid var(--so-border-color);
304
+ background: transparent;
305
+ border-radius: 9999px;
306
+ width: 2.75rem;
307
+ height: 2.75rem;
308
+ display: inline-flex;
309
+ align-items: center;
310
+ justify-content: center;
311
+ cursor: pointer;
312
+ color: rgba(0,0,0,0.8);
313
+ }
314
+ .iconbtn:hover{ background: rgba(0,0,0,0.04); }
315
+ .menubtn{
316
+ display: none;
317
+ color: rgb(204, 0, 0);
318
+ }
319
+ .menubtn[data-open="true"]{
320
+ background: rgb(204, 0, 0);
321
+ border-color: rgb(204, 0, 0);
322
+ color: #fff;
323
+ }
324
+ .menubtn[data-open="true"]:hover{
325
+ background: rgb(204, 0, 0);
326
+ }
327
+
328
+ /* Second row */
329
+ .secondbar{
330
+ width: 100%;
331
+ /*margin-block: 0.4rem;*/
332
+ }
333
+
334
+ .sectionnav ul{
335
+ list-style: none;
336
+ display: flex;
337
+ gap: 1.5rem;
338
+ margin: 0;
339
+ padding: 0;
340
+ padding-top: var(--so-space-2, 0.5rem);
341
+ align-items: baseline;
342
+ /*border-bottom: 1px solid rgba(0,0,0,0.08);*/
343
+ }
344
+ .sectionnav a{
345
+ text-decoration: none;
346
+ font-weight: 900;
347
+ font-size: 1.5rem;
348
+ letter-spacing: -0.01em;
349
+ color: var(--so-fg, rgb(47, 72, 88));
350
+ padding-bottom: 0.45rem;
351
+ border-bottom: 1px solid transparent;
352
+ margin-bottom: -1px;
353
+ display: inline-block;
354
+ }
355
+ .sectionnav a:hover{
356
+ color: rgb(204, 0, 0);
357
+ }
358
+ .sectionnav a[aria-current="page"]{
359
+ color: rgb(204, 0, 0);
360
+ border-bottom: 1px solid rgb(204, 0, 0);
361
+ }
362
+
363
+ /* Responsive: hide links + show menu */
364
+ @media (max-width: 992px){
365
+ .topnav ul{ gap: 2rem; }
366
+ }
367
+ @media (max-width: 768px){
368
+ .topnav{ display: none; }
369
+ .sectionnav a{ font-size: 1.35rem; }
370
+ .menubtn{ display: inline-flex; }
371
+ }
372
+
373
+ /* Mobile panel */
374
+ .mobile-panel{ display: none; border-top: 1px solid var(--so-border-color); }
375
+ .mobile-panel-inner{ padding-block: 0.75rem 1.25rem; }
376
+ @media (max-width: 768px){
377
+ .mobile-panel{
378
+ display: block;
379
+ border: 0;
380
+ }
381
+ .mobile-panel-inner[data-open="false"]{ display: none; }
382
+ }
383
+
384
+ .mobile-group{ margin-top: 0.75rem; }
385
+ .mobile-title{ font-weight: 900; margin-bottom: 0.5rem; }
386
+
387
+ .mobile-list{
388
+ list-style: none;
389
+ margin: 0;
390
+ padding: 0;
391
+ display: grid;
392
+ gap: 0;
393
+
394
+ border-top: 1px solid var(--so-border-color);
395
+ border-bottom: 1px solid var(--so-border-color);
396
+ /*border-radius: 0.6rem;*/
397
+ overflow: hidden; /* clips inner items to rounded corners */
398
+ }
399
+
400
+ .mobile-list a{
401
+ display: block;
402
+ padding: 0.65rem 0.75rem;
403
+ text-decoration: none;
404
+ font-size: 1rem;
405
+
406
+ border: 0; /* remove per-item border */
407
+ border-radius: 0; /* only the container rounds */
408
+ background: rgba(255,255,255,0.75);
409
+ color: var(--so-fg, rgb(47, 72, 88));
410
+ font-weight: 400;
411
+ }
412
+
413
+ /* add separators (1px) between items */
414
+ .mobile-list li + li a{
415
+ border-top: 1px solid var(--so-border-color);
416
+ }
417
+
418
+ .mobile-list a:hover{
419
+ color: rgb(204, 0, 0);
420
+ background: rgba(255,255,255,0.95);
421
+ }
422
+ `).replace(/^[\t ]+/gm, "").trim() + "\n";
423
+ }
424
+ }
425
+ SoHeader.observedAttributes = ["top-nav", "section-nav", "active-section", "logo-href", "site-name"];
426
+ function burgerIcon() {
427
+ return `
428
+ <svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
429
+ <path fill="currentColor" d="M4 7h16v2H4V7zm0 6h16v2H4v-2zm0 6h16v2H4v-2z"/>
430
+ </svg>`;
431
+ }
432
+ function closeIcon() {
433
+ return `
434
+ <svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
435
+ <path fill="currentColor" d="M18.3 5.71 12 12l6.3 6.29-1.41 1.42L10.59 13.4l-6.3 6.31-1.41-1.42L9.17 12 2.88 5.71l1.41-1.42 6.3 6.31 6.3-6.31 1.41 1.42Z"/>
436
+ </svg>`;
437
+ }
@@ -0,0 +1,50 @@
1
+ <!doctype html>
2
+ <html lang="de">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>so-header demo</title>
7
+ <link rel="stylesheet" href="../styles/reset.css">
8
+ <link rel="stylesheet" href="../styles/fonts.css">
9
+ <link rel="stylesheet" href="../styles/tokens.css">
10
+ <script type="module" src="../index.js"></script>
11
+ <style>
12
+ body{ background: var(--so-bg); color: var(--so-fg); font-family: var(--so-font-family); }
13
+ main{ padding: 2rem 0; }
14
+ .so-container{ max-width: var(--so-container-size-full); margin-inline:auto; padding-inline: var(--so-container-padding); }
15
+ .hero{
16
+ height: 260px;
17
+ border-radius: 12px;
18
+ background: linear-gradient(135deg, rgba(0,0,0,0.08), rgba(0,0,0,0.02));
19
+ border: 1px solid rgba(0,0,0,0.08);
20
+ display:flex;
21
+ align-items:flex-end;
22
+ padding: 1.25rem;
23
+ font-size: clamp(1.5rem, 3.5vw, 3rem);
24
+ font-weight: 900;
25
+ }
26
+ </style>
27
+ </head>
28
+ <body>
29
+ <so-header></so-header>
30
+ <so-breadcrumb>
31
+ <so-breadcrumb-item href="https://so.ch">so.ch</so-breadcrumb-item>
32
+ <so-breadcrumb-item href="https://so.ch/verwaltung/">Verwaltung</so-breadcrumb-item>
33
+ <so-breadcrumb-item href="https://so.ch/verwaltung/bau-und-justizdepartement/">Bau- und Justizdepartement</so-breadcrumb-item>
34
+ <so-breadcrumb-item href="https://so.ch/verwaltung/bau-und-justizdepartement/amt-fuer-geoinformation/" isCurrentPage>
35
+ Amt für Geoinformation
36
+ </so-breadcrumb-item>
37
+ </so-breadcrumb>
38
+
39
+ <main>
40
+ <div class="so-container">
41
+ <div class="hero">Willkommen beim Kanton Solothurn</div>
42
+ </div>
43
+ </main>
44
+
45
+ <script type="module">
46
+ const header = document.querySelector("so-header");
47
+ header?.addEventListener("so-section-select", (e) => console.log("section select", e.detail));
48
+ </script>
49
+ </body>
50
+ </html>
@@ -0,0 +1,3 @@
1
+ import { SoBreadcrumb, SoBreadcrumbItem } from "./components/so-breadcrumb.js";
2
+ import { SoHeader } from "./components/so-header.js";
3
+ export { SoBreadcrumb, SoBreadcrumbItem, SoHeader };
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { SoBreadcrumb, SoBreadcrumbItem } from "./components/so-breadcrumb.js";
2
+ import { SoHeader } from "./components/so-header.js";
3
+ if (!customElements.get("so-header")) {
4
+ customElements.define("so-header", SoHeader);
5
+ }
6
+ if (!customElements.get("so-breadcrumb")) {
7
+ customElements.define("so-breadcrumb", SoBreadcrumb);
8
+ }
9
+ if (!customElements.get("so-breadcrumb-item")) {
10
+ customElements.define("so-breadcrumb-item", SoBreadcrumbItem);
11
+ }
12
+ export { SoBreadcrumb, SoBreadcrumbItem, SoHeader };