Explorar el Código

placeholder simplified grid view

Justin Rhee hace 3 años
padre
commit
fac66734cf

BIN
dashboard/src/assets/search.png


BIN
dashboard/src/assets/status-healthy.png


BIN
dashboard/src/assets/time.png


+ 126 - 0
dashboard/src/components/porter/SearchBar.tsx

@@ -0,0 +1,126 @@
+import React, { useEffect, useState } from "react";
+import styled from "styled-components";
+
+import search from "assets/search.png";
+
+type Props = {
+  placeholder: string;
+  width?: string;
+  value: string;
+  setValue: (value: string) => void;
+  label?: string | React.ReactNode;
+  height?: string;
+  type?: string;
+  error?: string;
+  children?: React.ReactNode;
+};
+
+const SearchBar: React.FC<Props> = ({
+  placeholder,
+  width,
+  value,
+  setValue,
+  label,
+  height,
+  type,
+  error,
+  children,
+}) => {
+  return (
+    <Block width={width}>
+      {
+        label && (
+          <Label>{label}</Label>
+        )
+      }
+      <StyledSearchBar
+        width={width}
+        height={height}
+        hasError={(error && true) || (error === "")}
+      >
+        <Icon src={search} />
+        <Input
+          value={value}
+          onChange={e => setValue(e.target.value)}
+          placeholder={placeholder}
+          type={type || "text"}
+        />
+        {
+          error && (
+            <Error>
+              <i className="material-icons">error</i>
+              {error}
+            </Error>
+          )
+        }
+      </StyledSearchBar>
+      {children}
+    </Block>
+  );
+};
+
+export default SearchBar;
+
+const Icon = styled.img`
+  position: absolute;
+  left: 12px;
+  top: 50%;
+  opacity: 0.6;
+  transform: translateY(-50%);
+  height: 11px;
+`;
+
+const Block = styled.div<{
+  width: string;
+}>`
+  display: block;
+  position: relative;
+  width: ${props => props.width || "200px"};
+`;
+
+const Label = styled.div`
+  font-size: 13px;
+  color: #aaaabb;
+  margin-bottom: 10px;
+`;
+
+const Error = styled.div`
+  display: flex;
+  align-items: center;
+  font-size: 13px;
+  color: #ff3b62;
+  margin-top: 10px;
+
+  > i {
+    font-size: 18px;
+    margin-right: 5px;
+  }
+`;
+
+const StyledSearchBar = styled.div<{
+  width: string;
+  height: string;
+  hasError: boolean;
+}>`
+  height: ${props => props.height || "30px"};
+  padding: 5px 10px;
+  width: ${props => props.width || "200px"};
+  color: #ffffff;
+  font-size: 13px;
+  border-radius: 5px;
+  background: ${props => props.theme.fg};
+
+  border: 1px solid ${props => props.hasError ? "#ff3b62" : "#494b4f"};
+  :hover {
+    border: 1px solid ${props => props.hasError ? "#ff3b62" : "#7a7b80"};
+  }
+`;
+
+const Input = styled.input`
+  outline: none;
+  background: #00000000;
+  border: none;
+  width: 100%;
+  height: 100%;
+  padding-left: 23px;
+`;

+ 76 - 14
dashboard/src/main/home/app-dashboard/AppDashboard.tsx

@@ -2,6 +2,9 @@ import React, { useEffect, useState, useContext } from "react";
 import styled from "styled-components";
 
 import web from "assets/web.png";
+import github from "assets/github.png";
+import time from "assets/time.png";
+import healthy from "assets/status-healthy.png";
 
 import { Context } from "shared/Context";
 import api from "shared/api";
@@ -10,14 +13,35 @@ import DashboardHeader from "../cluster-dashboard/DashboardHeader";
 import Container from "components/porter/Container";
 import Button from "components/porter/Button";
 import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
+import SearchBar from "components/porter/SearchBar";
 
 type Props = {
 };
 
+const icons = [
+  "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/ruby/ruby-plain.svg",
+  "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nodejs/nodejs-plain.svg",
+  "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/python/python-plain.svg",
+  "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/go/go-original-wordmark.svg",
+  web,
+];
+
+const namespaceBlacklist = [
+  "cert-manager",
+  "default",
+  "ingress-nginx",
+  "kube-node-lease",
+  "kube-public",
+  "kube-system",
+  "monitoring",
+];
+
 const AppDashboard: React.FC<Props> = ({
 }) => {
   const { currentProject, currentCluster } = useContext(Context);
   const [apps, setApps] = useState([]);
+  const [searchValue, setSearchValue] = useState("");
   const [isLoading, setIsLoading] = useState(true);
 
   const getApps = async () => {
@@ -50,36 +74,74 @@ const AppDashboard: React.FC<Props> = ({
         disableLineBreak
       />
       <Container row spaced>
-        <Button onClick={() => console.log("cool")} height="30px">
+        <SearchBar 
+          value={searchValue}
+          setValue={setSearchValue}
+          placeholder="Search applications . . ."
+          width="100%"
+        />
+        <Spacer inline x={2} />
+        <Button onClick={() => console.log("cool")} height="30px" width="150px">
           <I className="material-icons">add</I> Add application
         </Button>
-        <div>x/o</div>
       </Container>
       <Spacer y={1} />
       <GridList>
-        {apps.map((app: any) => {
-          return (
-            <Block></Block>
-          );
+        {apps.map((app: any, i: number) => {
+          if (!namespaceBlacklist.includes(app.name)) {
+            return (
+              <Block>
+                <Text size={14}>
+                  <Icon src={icons[i % icons.length]} />
+                  {app.name}
+                </Text>
+                <StatusIcon src={healthy} />
+                <Text size={13} color="#ffffff44">
+                  <SmallIcon opacity="0.6" src={github} />
+                  porter-dev/porter
+                </Text>
+                <Text size={13} color="#ffffff44">
+                  <SmallIcon opacity="0.4" src={time} />
+                  Updated 6:35 PM on 4/23/2023
+                </Text>
+              </Block>
+            );
+          }
         })}
       </GridList>
+      <Spacer y={5} />
     </StyledAppDashboard>
   );
 };
 
 export default AppDashboard;
 
+const StatusIcon = styled.img`
+  position: absolute;
+  top: 20px;
+  right: 20px;
+  height: 18px;
+`;
+
+const Icon = styled.img`
+  height: 18px;
+  margin-right: 15px;
+`;
+
+const SmallIcon = styled.img<{ opacity?: string }>`
+  margin-left: 2px;
+  height: 14px;
+  opacity: ${props => props.opacity || 1};
+  margin-right: 10px;
+`;
+
 const Block = styled.div`
-  align-items: center;
-  user-select: none;
-  display: flex;
-  font-size: 13px;
-  padding: 3px 0px 5px;
+  height: 150px;
   flex-direction: column;
-  align-item: center;
+  display: flex;
   justify-content: space-between;
-  height: 170px;
   cursor: pointer;
+  padding: 20px;
   color: ${props => props.theme.text.primary};
   position: relative;
   border-radius: 5px;
@@ -104,7 +166,7 @@ const GridList = styled.div`
   display: grid;
   grid-column-gap: 25px;
   grid-row-gap: 25px;
-  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
 `;
 
 const I = styled.i`