testio-tailwind 3.26.1 → 3.27.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.
- package/.eleventy.js +1 -1
- package/package.json +1 -1
- package/src/_includes/header.njk +14 -0
- package/src/assets/scripts/app.js +1 -0
- package/src/assets/scripts/modules/component_search.js +144 -0
- package/src/assets/stylesheets/app.css +1 -0
- package/src/assets/stylesheets/components/manager/manager_sidebar.css +0 -5
- package/src/assets/stylesheets/components/radio_tabs.css +1 -1
- package/src/assets/stylesheets/components/search.css +5 -0
- package/src/assets/stylesheets/components/stepper.css +116 -0
- package/src/pages/components/stepper.haml +67 -0
- package/src/search-index.11ty.js +57 -0
package/.eleventy.js
CHANGED
|
@@ -45,7 +45,7 @@ module.exports = function (eleventyConfig) {
|
|
|
45
45
|
includes: "_includes",
|
|
46
46
|
layouts: "_layouts"
|
|
47
47
|
},
|
|
48
|
-
templateFormats: ["html", "md", "njk", "pug", "haml"],
|
|
48
|
+
templateFormats: ["html", "md", "njk", "pug", "haml", "11ty.js"],
|
|
49
49
|
htmlTemplateEngine: "njk",
|
|
50
50
|
|
|
51
51
|
// 1.1 Enable eleventy to pass dirs specified above
|
package/package.json
CHANGED
package/src/_includes/header.njk
CHANGED
|
@@ -61,6 +61,20 @@
|
|
|
61
61
|
</details>
|
|
62
62
|
|
|
63
63
|
<div class="navlinks right">
|
|
64
|
+
<details class="popover-wrapper dropright component-search">
|
|
65
|
+
<summary class="btn btn-square navlink">
|
|
66
|
+
<span class="icon icon-search" aria-hidden="true"></span>
|
|
67
|
+
<span class="sr-only">Search components</span>
|
|
68
|
+
</summary>
|
|
69
|
+
<div class="popover-menu search">
|
|
70
|
+
<form class="form-search inverted" role="search">
|
|
71
|
+
<input type="search" class="form-control" id="component-search-input" placeholder="Search components..." autocomplete="off" aria-label="Search components">
|
|
72
|
+
<button type="button" class="btn btn-clear" aria-label="Clear search"></button>
|
|
73
|
+
<button type="button" class="btn btn-submit" aria-label="Search"></button>
|
|
74
|
+
</form>
|
|
75
|
+
<div id="component-search-results" class="list-searchresults" role="listbox" aria-label="Search results" hidden></div>
|
|
76
|
+
</div>
|
|
77
|
+
</details>
|
|
64
78
|
<button onclick="(() => document.body.classList.toggle('dark'))()" class="navlink">
|
|
65
79
|
<svg class="w-icon h-icon fill-purple hidden dark:block" fill="currentColor" viewBox="0 0 20 20">
|
|
66
80
|
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component search - fetches search-index.json and filters results on input.
|
|
3
|
+
* Uses the search popover pattern from /forms/#Search.
|
|
4
|
+
* Shows popover with max 8 results, navigates on click.
|
|
5
|
+
*/
|
|
6
|
+
const MAX_RESULTS = 8;
|
|
7
|
+
|
|
8
|
+
let searchIndex = [];
|
|
9
|
+
|
|
10
|
+
async function loadSearchIndex() {
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch("/search-index.json");
|
|
13
|
+
if (res.ok) {
|
|
14
|
+
searchIndex = await res.json();
|
|
15
|
+
}
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.warn("Component search: could not load index", e);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function slugifySection(name) {
|
|
22
|
+
return name.charAt(0).toUpperCase() + name.slice(1).replace(/([A-Z])/g, " $1").trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function search(term) {
|
|
26
|
+
if (!term || term.length < 2) return [];
|
|
27
|
+
const lower = term.toLowerCase();
|
|
28
|
+
return searchIndex
|
|
29
|
+
.filter(
|
|
30
|
+
(item) =>
|
|
31
|
+
item.title.toLowerCase().includes(lower) ||
|
|
32
|
+
(item.section && slugifySection(item.section).toLowerCase().includes(lower))
|
|
33
|
+
)
|
|
34
|
+
.slice(0, MAX_RESULTS);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function showResults(results) {
|
|
38
|
+
const container = document.getElementById("component-search-results");
|
|
39
|
+
if (!container) return;
|
|
40
|
+
|
|
41
|
+
if (results.length === 0) {
|
|
42
|
+
container.innerHTML = '<div class="component-search-empty">No components found</div>';
|
|
43
|
+
container.hidden = false;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
container.innerHTML = results
|
|
48
|
+
.map(
|
|
49
|
+
(item) =>
|
|
50
|
+
`<a href="${escapeHtml(item.url)}" class="issue-item">
|
|
51
|
+
<span class="issue-title">${escapeHtml(item.title)}</span>
|
|
52
|
+
<span class="issue-info">${escapeHtml(slugifySection(item.section))}</span>
|
|
53
|
+
</a>`
|
|
54
|
+
)
|
|
55
|
+
.join("");
|
|
56
|
+
container.hidden = false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function hideResults() {
|
|
60
|
+
const container = document.getElementById("component-search-results");
|
|
61
|
+
if (container) {
|
|
62
|
+
container.hidden = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function escapeHtml(str) {
|
|
67
|
+
const div = document.createElement("div");
|
|
68
|
+
div.textContent = str;
|
|
69
|
+
return div.innerHTML;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function initComponentSearch() {
|
|
73
|
+
const componentSearch = document.querySelector(".component-search");
|
|
74
|
+
const input = document.getElementById("component-search-input");
|
|
75
|
+
const container = document.getElementById("component-search-results");
|
|
76
|
+
const details = componentSearch?.closest("details");
|
|
77
|
+
if (!input || !container) return;
|
|
78
|
+
|
|
79
|
+
let debounceTimer;
|
|
80
|
+
|
|
81
|
+
// Focus input when popover opens
|
|
82
|
+
details?.addEventListener("toggle", () => {
|
|
83
|
+
if (details.open) {
|
|
84
|
+
setTimeout(() => input.focus(), 0);
|
|
85
|
+
hideResults();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
input.addEventListener("input", () => {
|
|
90
|
+
clearTimeout(debounceTimer);
|
|
91
|
+
const term = input.value.trim();
|
|
92
|
+
if (term.length < 2) {
|
|
93
|
+
hideResults();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
debounceTimer = setTimeout(() => {
|
|
97
|
+
showResults(search(term));
|
|
98
|
+
}, 150);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
input.addEventListener("focus", () => {
|
|
102
|
+
const term = input.value.trim();
|
|
103
|
+
if (term.length >= 2) {
|
|
104
|
+
showResults(search(term));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
input.addEventListener("keydown", (e) => {
|
|
109
|
+
if (e.key === "Escape") {
|
|
110
|
+
hideResults();
|
|
111
|
+
if (details) {
|
|
112
|
+
details.removeAttribute("open");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Clear button
|
|
118
|
+
const clearBtn = componentSearch?.querySelector(".btn-clear");
|
|
119
|
+
clearBtn?.addEventListener("click", (e) => {
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
input.value = "";
|
|
122
|
+
hideResults();
|
|
123
|
+
input.focus();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Prevent form submit
|
|
127
|
+
const form = componentSearch?.querySelector("form");
|
|
128
|
+
form?.addEventListener("submit", (e) => {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
const term = input.value.trim();
|
|
131
|
+
if (term.length >= 2) {
|
|
132
|
+
showResults(search(term));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Initialize when DOM ready
|
|
138
|
+
if (document.readyState === "loading") {
|
|
139
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
140
|
+
loadSearchIndex().then(initComponentSearch);
|
|
141
|
+
});
|
|
142
|
+
} else {
|
|
143
|
+
loadSearchIndex().then(initComponentSearch);
|
|
144
|
+
}
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
@import './components/scrollbars.css' layer(components);
|
|
61
61
|
@import './components/search.css' layer(components);
|
|
62
62
|
@import './components/sections.css' layer(components);
|
|
63
|
+
@import './components/stepper.css' layer(components);
|
|
63
64
|
@import './components/select.css' layer(components);
|
|
64
65
|
@import './components/selectable_token.css' layer(components);
|
|
65
66
|
@import './components/sidebar.css' layer(components);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/* Stepper - Multi-step wizard component (converted from testio-designsystem) */
|
|
2
|
+
|
|
3
|
+
.stepper {
|
|
4
|
+
@apply flex flex-row items-center flex-wrap gap-0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.stepper-step {
|
|
8
|
+
@apply flex flex-row items-center shrink-0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.stepper-step .stepper-icon {
|
|
12
|
+
@apply flex items-center justify-center shrink-0 w-8 h-8 text-base font-semibold rounded-full border-2 border-gray-300 bg-white text-label-color;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.stepper-step .stepper-label {
|
|
16
|
+
@apply ml-xs font-semibold text-sm text-appbody-textcolor;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Connector line between steps */
|
|
20
|
+
.stepper-step:not(:last-child)::after {
|
|
21
|
+
content: "";
|
|
22
|
+
@apply w-8 md:w-12 mx-xs h-px bg-bordercolor self-center;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Completed state */
|
|
26
|
+
.stepper-step.completed .stepper-icon {
|
|
27
|
+
@apply border-success bg-success text-white;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.stepper-step.completed .stepper-icon .icon {
|
|
31
|
+
@apply text-white;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.stepper-step.completed .stepper-label {
|
|
35
|
+
@apply text-success;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Active state */
|
|
39
|
+
.stepper-step.active .stepper-icon {
|
|
40
|
+
@apply border-none bg-primary text-white border-2;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.stepper-step.active .stepper-icon .icon {
|
|
44
|
+
@apply text-white;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.stepper-step.active .stepper-label {
|
|
48
|
+
@apply text-primary;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Pending state (default) */
|
|
52
|
+
.stepper-step:not(.completed):not(.active) .stepper-icon {
|
|
53
|
+
@apply border-0 bg-gray-lighter text-label-color;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.stepper-step:not(.completed):not(.active) .stepper-label {
|
|
57
|
+
@apply text-label-color;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Dark mode */
|
|
61
|
+
.dark .stepper-step .stepper-icon {
|
|
62
|
+
@apply border-bordercolor bg-gray-800 text-label-color;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.dark .stepper-step.completed .stepper-icon {
|
|
66
|
+
@apply border-success bg-success;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.dark .stepper-step.active .stepper-icon {
|
|
70
|
+
@apply border-primary bg-primary;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.dark .stepper-step:not(.completed):not(.active) .stepper-icon {
|
|
74
|
+
@apply bg-gray-800;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ===== Vertical stepper ===== */
|
|
78
|
+
|
|
79
|
+
.stepper-vertical {
|
|
80
|
+
@apply flex flex-col items-stretch gap-0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.stepper-vertical .stepper-label {
|
|
84
|
+
@apply ml-0 pt-2;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.stepper-vertical .stepper-step {
|
|
88
|
+
@apply flex flex-row items-start gap-0 pb-md last:pb-0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.stepper-vertical .stepper-step .stepper-icon {
|
|
92
|
+
@apply shrink-0 relative z-10;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.stepper-vertical .stepper-step .stepper-container {
|
|
96
|
+
@apply flex flex-col flex-1 min-w-0 ml-xs gap-0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.stepper-vertical .stepper-step .stepper-container .stepper-label {
|
|
100
|
+
@apply mb-xxs;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Hide horizontal connector for vertical layout */
|
|
104
|
+
.stepper-vertical .stepper-step:not(:last-child)::after {
|
|
105
|
+
content: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Vertical connector line - runs from bottom of icon down to next step */
|
|
109
|
+
.stepper-vertical .stepper-step:not(:last-child) {
|
|
110
|
+
@apply relative;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.stepper-vertical .stepper-step:not(:last-child)::before {
|
|
114
|
+
content: "";
|
|
115
|
+
@apply block absolute left-4 top-8 bottom-0 w-px bg-bordercolor -translate-x-1/2 z-0;
|
|
116
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: components
|
|
3
|
+
title: Stepper
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
%p.mb-heading Stepper component for multi-step wizard flows. Use for checkout, onboarding, or any sequential process.
|
|
7
|
+
%p.mb-sm
|
|
8
|
+
Completed steps show a check icon. The active step is highlighted. Pending steps are muted.
|
|
9
|
+
.mb-md
|
|
10
|
+
.stepper
|
|
11
|
+
.stepper-step.completed
|
|
12
|
+
.stepper-icon
|
|
13
|
+
%span.icon.icon-check
|
|
14
|
+
.stepper-label Step 1
|
|
15
|
+
.stepper-step.active
|
|
16
|
+
.stepper-icon 2
|
|
17
|
+
.stepper-label Step 2
|
|
18
|
+
.stepper-step
|
|
19
|
+
.stepper-icon 3
|
|
20
|
+
.stepper-label Step 3
|
|
21
|
+
.stepper-step
|
|
22
|
+
.stepper-icon 4
|
|
23
|
+
.stepper-label Step 4
|
|
24
|
+
|
|
25
|
+
%p.mb-heading.mt-lg Four-step wizard example with all completed except the last.
|
|
26
|
+
.mb-md
|
|
27
|
+
.stepper
|
|
28
|
+
.stepper-step.completed
|
|
29
|
+
.stepper-icon
|
|
30
|
+
%span.icon.icon-check
|
|
31
|
+
.stepper-label Details
|
|
32
|
+
.stepper-step.completed
|
|
33
|
+
.stepper-icon
|
|
34
|
+
%span.icon.icon-check
|
|
35
|
+
.stepper-label Payment
|
|
36
|
+
.stepper-step.completed
|
|
37
|
+
.stepper-icon
|
|
38
|
+
%span.icon.icon-check
|
|
39
|
+
.stepper-label Review
|
|
40
|
+
.stepper-step.active
|
|
41
|
+
.stepper-icon 4
|
|
42
|
+
.stepper-label Confirm
|
|
43
|
+
|
|
44
|
+
%p.mb-heading.mt-lg Vertical stepper variant. Add the .stepper-vertical class to the container. Use .stepper-container to wrap the label and content for each step.
|
|
45
|
+
.mb-md
|
|
46
|
+
.stepper.stepper-vertical
|
|
47
|
+
.stepper-step.completed
|
|
48
|
+
.stepper-icon
|
|
49
|
+
%span.icon.icon-check
|
|
50
|
+
.stepper-container
|
|
51
|
+
.stepper-label Enter your details
|
|
52
|
+
%p.text-sm.text-label-color.mt-xxs Name, email, and shipping address.
|
|
53
|
+
.stepper-step.active
|
|
54
|
+
.stepper-icon 2
|
|
55
|
+
.stepper-container
|
|
56
|
+
.stepper-label Choose payment method
|
|
57
|
+
%p.text-sm.text-label-color.mt-xxs Credit card or PayPal.
|
|
58
|
+
.stepper-step
|
|
59
|
+
.stepper-icon 3
|
|
60
|
+
.stepper-container
|
|
61
|
+
.stepper-label Review order
|
|
62
|
+
%p.text-sm.text-label-color.mt-xxs Verify your order before confirming.
|
|
63
|
+
.stepper-step
|
|
64
|
+
.stepper-icon 4
|
|
65
|
+
.stepper-container
|
|
66
|
+
.stepper-label Confirmation
|
|
67
|
+
%p.text-sm.text-label-color.mt-xxs Order complete.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module.exports = class {
|
|
2
|
+
data() {
|
|
3
|
+
return {
|
|
4
|
+
permalink: "/search-index.json",
|
|
5
|
+
eleventyExcludeFromCollections: true,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
render(data) {
|
|
10
|
+
const collectionMap = {
|
|
11
|
+
components: "/components/",
|
|
12
|
+
button: "/buttons/",
|
|
13
|
+
forms: "/forms/",
|
|
14
|
+
tables: "/tables/",
|
|
15
|
+
charts: "/charts/",
|
|
16
|
+
layout: "/layout/",
|
|
17
|
+
navigation: "/navigation/",
|
|
18
|
+
typography: "/typography/",
|
|
19
|
+
agenticqa: "/agenticqa/",
|
|
20
|
+
icons: "/icons/",
|
|
21
|
+
issuing: "/issuing/",
|
|
22
|
+
layouts: null, // layouts have their own URLs
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const items = [];
|
|
26
|
+
|
|
27
|
+
for (const [collName, baseUrl] of Object.entries(collectionMap)) {
|
|
28
|
+
if (!baseUrl || !data.collections[collName]) continue;
|
|
29
|
+
const collection = data.collections[collName];
|
|
30
|
+
for (const item of collection) {
|
|
31
|
+
if (item.data && item.data.title) {
|
|
32
|
+
const anchor = encodeURIComponent(item.data.title);
|
|
33
|
+
items.push({
|
|
34
|
+
title: item.data.title,
|
|
35
|
+
url: baseUrl + "#" + anchor,
|
|
36
|
+
section: collName,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Add layout pages (standalone pages with their own URL)
|
|
43
|
+
if (data.collections.layouts) {
|
|
44
|
+
for (const item of data.collections.layouts) {
|
|
45
|
+
if (item.data && item.data.title && item.url) {
|
|
46
|
+
items.push({
|
|
47
|
+
title: item.data.title,
|
|
48
|
+
url: item.url.startsWith("/") ? item.url : "/" + item.url,
|
|
49
|
+
section: "layouts",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return JSON.stringify(items);
|
|
56
|
+
}
|
|
57
|
+
};
|