diff --git a/web/assets/css/dropdown.css b/web/assets/css/dropdown.css new file mode 100644 index 0000000..602ffc4 --- /dev/null +++ b/web/assets/css/dropdown.css @@ -0,0 +1,145 @@ +.libraries-dropdown-container { + display: flex; +} + +#libraries-select-button { + border-radius: 0.25rem; + border-width: 1px; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; + letter-spacing: -0.025em; + line-height: 1.5; + color: #4b5563; + + display: flex; + align-items: center; + height: 40px; + padding: 1rem; +} + +#libraries-select-button svg { + width: 7px; + height: 7px; + pointer-events: none; + transition: transform 150ms; + color: #999; +} + +#libraries-select-button[data-dropdown-open="true"] svg { + transform: rotate(180deg); +} + +#dropdownHelper { + overflow: hidden; + border: 1px solid #e8e8e8; +} + +.container-list { + margin-top: 0.25rem; + font-size: 0.875rem; + line-height: 1.25rem; + color: #374151; + + overflow-y: auto; + max-height: 50vh; +} + +.container-list::-webkit-scrollbar { + width: 5px; +} + +.container-list::-webkit-scrollbar-thumb { + background: #e3e3e3; + border-radius: 6px; +} +.container-list::-webkit-scrollbar-track { + background: #f5f5f5; +} + +.container-list .list-item { + padding: 0.75rem; +} + +.container-list .sublist-item { + display: grid; + grid-template-columns: 1fr 100px; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-right: 1rem; + justify-content: space-between; + align-items: center; +} + +.container-list .sublist-item:hover { + background-color: #8447d114; + cursor: pointer; +} + +.list-item .list-item-label { + color: #3e525b; + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 24px; /* 171.429% */ + letter-spacing: 0.14px; +} + +.list-item:not(:first-of-type) { + border-top: 1px solid #e3e3e3; +} + +.control { + display: flex; + padding: 0.5rem; + gap: 0.5rem; + align-items: center; + border-radius: 0.25rem; +} + +.control .checkbox { + border-radius: 0.25rem; + border-color: #d1d5db; + width: 1rem; + height: 1rem; + background-color: #f3f4f6; + color: #8447d1; +} + +.control .checkbox:focus { + box-shadow: initial; +} + +.control .label { + font-weight: 400; + color: #4b5563; + font-size: 14px; +} + +.dark #libraries-select-button { + color: #ffffff; + border: 1px solid #383c43; +} + +.dark #dropdownHelper { + background-color: #2a2d32; + border: 1px solid #383c43; +} + +.dark .control .checkbox { + background-color: transparent; + border: 1px solid #00000060; +} + +.dark .control .label { + color: #ffffffd9; +} + +.dark .list-item span { + color: #ffffff; +} + +.dark .list-item:not(:first-of-type) { + border-top: 1px solid #383c43; +} diff --git a/web/assets/css/styles.css b/web/assets/css/styles.css index b77ed02..af9cffb 100644 --- a/web/assets/css/styles.css +++ b/web/assets/css/styles.css @@ -136,6 +136,7 @@ a { border: 1px solid #e6e6e6; background: #fff; position: relative; + overflow: hidden; } .dark .share-url__container { @@ -153,6 +154,7 @@ a { line-height: 24px; letter-spacing: 0.12px; border: none; + outline: none; } .dark .share-url__input { @@ -178,6 +180,7 @@ a { .share-url__input:focus { outline: none; + border: none; } .share-url__copy { diff --git a/web/assets/js/main.js b/web/assets/js/main.js index 2a59a2b..88bc8d8 100644 --- a/web/assets/js/main.js +++ b/web/assets/js/main.js @@ -15,8 +15,15 @@ */ import { AceEditor } from "./editor.js"; +import { groupBy } from "./utils/group.js"; -const selectInstance = NiceSelect.bind(document.getElementById("examples")); +const librariesSelect = document.getElementById("dropdownHelper"); +const librariesSelectButton = document.getElementById( + "libraries-select-button" +); + +const exampleSelect = document.getElementById("examples"); +const exampleSelectInstance = NiceSelect.bind(exampleSelect); // Add the following polyfill for Microsoft Edge 17/18 support: // @@ -185,19 +192,7 @@ fetch("../assets/data.json") // Dynamically set the CEL Go version document.getElementById("version").innerText = versions["cel-go"]; - // Load the examples into the select element - const examplesList = document.getElementById("examples"); - - const groupByCategory = examples.reduce((acc, example) => { - return { - ...acc, - [example.category]: [...(acc[example.category] ?? []), example], - }; - }, {}); - - const examplesByCategory = Object.entries(groupByCategory).map( - ([key, value]) => ({ label: key, value }) - ); + const examplesByCategory = groupBy(examples, "category"); examplesByCategory.forEach((example) => { const optGroup = document.createElement("optgroup"); @@ -220,25 +215,101 @@ fetch("../assets/data.json") } else if (example.label === "Blank") { return; } else { - examplesList.appendChild(optGroup); + exampleSelect.appendChild(optGroup); } }); const blankOption = document.createElement("option"); blankOption.innerText = "Blank"; blankOption.value = "Blank"; - examplesList.appendChild(blankOption); + exampleSelect.appendChild(blankOption); - selectInstance.update(); + exampleSelectInstance.update(); - examplesList.addEventListener("change", (event) => { + exampleSelect.addEventListener("change", (event) => { const example = examples.find( (example) => example.name === event.target.value ); celEditor.setValue(example.cel, -1); dataEditor.setValue(example.data, -1); }); + + loadLibrarySelect(examplesByCategory); }) .catch((err) => { console.error(err); }); + +function loadLibrarySelect(data) { + const list = document.createElement("ul"); + list.className = "container-list"; + list["aria-labelledby"] = "dropdownHelperButton"; + + data.forEach((item) => { + const listItem = document.createElement("li"); + listItem.className = "list-item"; + listItem.innerHTML = `${item.label}`; + + if (item.label === "default") return; + + item.value.forEach((value) => { + const sublist = document.createElement("ul"); + const sublistItem = document.createElement("li"); + sublistItem.className = "sublist-item"; + + const control = document.createElement("label"); + control.className = "control"; + control.htmlFor = value.name; + + const checkbox = document.createElement("input"); + checkbox.setAttribute("type", "checkbox"); + checkbox.setAttribute("id", value.name); + checkbox.className = "checkbox"; + + const label = document.createElement("span"); + label.className = "label"; + label.innerText = value.name; + + control.appendChild(checkbox); + control.appendChild(label); + + const showExampleButton = document.createElement("button"); + + sublistItem.onmouseover = (ev) => { + showExampleButton.innerText = "Show example"; + sublistItem.appendChild(showExampleButton); + showExampleButton.className = "show-example-text"; + showExampleButton.style.color = "#B076F9"; + showExampleButton.style.fontSize = "14px"; + showExampleButton.style.fontWeight = "500"; + + sublistItem.style.backgroundColor = "#8447d114"; + }; + + sublistItem.onmouseleave = (ev) => { + sublistItem.removeChild(showExampleButton); + sublistItem.style.backgroundColor = "initial"; + }; + + sublistItem.appendChild(control); + + sublist.appendChild(sublistItem); + listItem.appendChild(sublist); + }); + + list.append(listItem); + }); + + librariesSelect.appendChild(list); +} + +function isDropdownOpen() { + return librariesSelect.classList.contains("block"); +} + +librariesSelectButton.addEventListener("click", (ev) => { + librariesSelectButton.setAttribute( + "data-dropdown-open", + String(!isDropdownOpen()) + ); +}); diff --git a/web/assets/js/utils/group.js b/web/assets/js/utils/group.js new file mode 100644 index 0000000..05e91d8 --- /dev/null +++ b/web/assets/js/utils/group.js @@ -0,0 +1,18 @@ +export function groupBy(data, by) { + if (!by in data[0]) + throw new Error("You must provide a valid 'by' argument!"); + + const reducedData = data.reduce((acc, cur) => { + return { + ...acc, + [cur[by]]: [...(acc[cur[by]] ?? []), cur], + }; + }, {}); + + const groupedData = Object.entries(reducedData).map(([key, value]) => ({ + label: key, + value, + })); + + return groupedData; +} diff --git a/web/index.html b/web/index.html index 0d67a70..cf7f098 100644 --- a/web/index.html +++ b/web/index.html @@ -1,4 +1,4 @@ - + + + + CEL Playground - - - CEL Playground - - - - - - - - - - - - - - - - -
-
-
-
-
- - Expression - - (CEL format) -
- -
-
-
-
-
-
-
- - Input - - (YAML or JSON format) -
- -
-
-
-
-
- Output -
- -
-
-
-
- - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ Expression + (CEL format) +
+ +
+
+
+
+
+
+
+ Input + (YAML or JSON format) +
+ +
+
+
+
+
Output
+ +
+
+
+
+ + + + + + +