Add low level tree component

Bug: 273238466
Change-Id: Iacf886864de99b3d4882afcce42fea070519a285
diff --git a/ui/src/assets/widgets/tree.scss b/ui/src/assets/widgets/tree.scss
new file mode 100644
index 0000000..569ad9a
--- /dev/null
+++ b/ui/src/assets/widgets/tree.scss
@@ -0,0 +1,126 @@
+@import "theme";
+
+$indent: 20px;
+
+.pf-tree-left {
+  min-width: max-content;
+  padding: 2px 8px 2px 4px;
+  font-weight: 600;
+}
+
+.pf-tree-right {
+  padding: 2px 4px;
+}
+
+// In tree mode, the values and keys are represented simply using a nested set
+// of divs, where each child is pushed in with a left margin which creates the
+// effect of indenting nested subtrees.
+.pf-ptree {
+  .pf-tree-children {
+    padding-left: $indent;
+    border-left: dotted 1px gray;
+  }
+
+  .pf-tree-node {
+    display: grid;
+    width: max-content;
+    grid-template-columns: [left]auto [right]1fr;
+    border-radius: $pf-border-radius;
+
+    &:hover {
+      background: lightgray;
+    }
+
+    .pf-tree-left {
+      grid-column: left;
+      &:after {
+        content: ":";
+        font-weight: 600;
+        padding-left: 4px;
+        padding-right: 8px;
+      }
+    }
+
+    .pf-tree-right {
+      grid-column: right;
+    }
+  }
+}
+
+// In grid mode, right elements should be horizontally aligned, regardless
+// of indentation level.
+// "Subgrid" is a convenient tool for aligning nested grids to an outer grid's
+// columns, but it is not supported in Chrome as of March 2023.
+// See https://caniuse.com/css-subgrid
+// See https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Subgrid
+//
+// For future reference - this is what a subgrid implementation might look like:
+//
+// .pf-ptree-grid {
+//   display: grid;
+//   grid-template-columns: auto 1fr;
+//
+//   .pf-tree-children {
+//     display: grid;
+//     grid-column: span 2;
+//     grid-template-columns: subgrid;
+//     padding-left: $indent;
+//     border-left: dotted 1px gray;
+//   }
+
+//   .pf-tree-node {
+//     display: grid;
+//     grid-column: span 2;
+//     grid-template-columns: subgrid;
+//     width: max-content;
+//     border-radius: $pf-border-radius;
+
+//     &:hover {
+//       background: lightgray;
+//     }
+//   }
+// }
+
+@mixin indentation($max, $level) {
+  @if $level <= $max {
+    .pf-tree-children {
+      .pf-tree-left {
+        margin-left: $level * $indent;
+      }
+      @include indentation($max, $level + 1);
+    }
+  }
+}
+
+.pf-ptree-grid {
+  display: grid;
+  grid-template-columns: auto 1fr;
+
+  .pf-tree-children {
+    display: contents;
+  }
+
+  .pf-tree-node {
+    display: contents;
+
+    &:hover {
+      background: lightgray;
+    }
+
+    .pf-tree-left {
+      background: inherit;
+      border-radius: $pf-border-radius 0 0 $pf-border-radius;
+    }
+
+    .pf-tree-right {
+      background: inherit;
+      border-radius: 0 $pf-border-radius $pf-border-radius 0;
+    }
+  }
+
+  @include indentation(16, 1);
+}
+
+.pf-tree-children.pf-pgrid-hidden {
+  display: none;
+}