Add dashboard support for Google Material Symbols. Closes #2828. (#2834)

This commit is contained in:
Mike Burgess
2022-12-06 11:59:19 +00:00
committed by GitHub
parent a19fd60f39
commit d1624cd83b
20 changed files with 822 additions and 803 deletions

1
.gitignore vendored
View File

@@ -26,6 +26,7 @@
/ui/dashboard/.vscode
/ui/dashboard/build
/ui/dashboard/node_modules
/ui/dashboard/src/icons/materialSymbols.ts
/ui/dashboard/output
/ui/dashboard/yarn-debug.log*
/ui/dashboard/yarn-error.log*

View File

@@ -4,18 +4,22 @@
"private": true,
"main": "index.js",
"scripts": {
"prestart": "node scripts/setupMaterialSymbols.js",
"start": "if-node-version '>= 17' && craco --openssl-legacy-provider start || craco start",
"prebuid": "node scripts/setupMaterialSymbols.js",
"build": "GENERATE_SOURCEMAP=false if-node-version '>= 17' && craco --openssl-legacy-provider build || craco build",
"test": "craco test",
"eject": "react-scripts eject",
"prettify": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"",
"analyze": "source-map-explorer 'build/static/js/*.js'",
"storybook": "start-storybook -p 6006 -s public",
"prestorybook": "node scripts/setupMaterialSymbols.js",
"storybook": "NODE_OPTIONS=--openssl-legacy-provider start-storybook -p 6006 -s public",
"build-storybook": "build-storybook -s public"
},
"dependencies": {
"@headlessui/react": "1.7.4",
"@heroicons/react": "2.0.13",
"@material-symbols/svg-200": "0.4.1",
"@popperjs/core": "2.11.6",
"@supabase/sql-formatter": "4.0.3",
"color-convert": "2.0.1",
@@ -51,16 +55,16 @@
},
"devDependencies": {
"@craco/craco": "7.0.0",
"@storybook/addon-actions": "6.5.13",
"@storybook/addon-essentials": "6.5.13",
"@storybook/addon-links": "6.5.13",
"@storybook/addons": "6.5.13",
"@storybook/builder-webpack5": "6.5.13",
"@storybook/manager-webpack5": "6.5.13",
"@storybook/node-logger": "6.5.13",
"@storybook/addon-actions": "6.5.14",
"@storybook/addon-essentials": "6.5.14",
"@storybook/addon-links": "6.5.14",
"@storybook/addons": "6.5.14",
"@storybook/builder-webpack5": "6.5.14",
"@storybook/manager-webpack5": "6.5.14",
"@storybook/node-logger": "6.5.14",
"@storybook/preset-create-react-app": "4.1.2",
"@storybook/react": "6.5.13",
"@storybook/theming": "6.5.13",
"@storybook/react": "6.5.14",
"@storybook/theming": "6.5.14",
"@tailwindcss/forms": "0.5.3",
"@tailwindcss/line-clamp": "0.4.2",
"@tailwindcss/typography": "0.5.8",
@@ -68,15 +72,15 @@
"@testing-library/react": "13.4.0",
"@tsconfig/create-react-app": "1.0.3",
"@types/echarts": "4.9.16",
"@types/jest": "29.2.3",
"@types/jest": "29.2.4",
"@types/lodash": "4.14.191",
"@types/node": "18.11.10",
"@types/react": "18.0.25",
"@types/node": "18.11.11",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"autoprefixer": "10.4.13",
"circular-dependency-plugin": "5.2.2",
"if-node-version": "1.1.1",
"lint-staged": "13.0.4",
"lint-staged": "13.1.0",
"npm-run-all": "4.1.5",
"prettier": "2.8.0",
"process": "0.11.10",
@@ -84,7 +88,7 @@
"react-scripts": "5.0.1",
"source-map-explorer": "2.5.3",
"storybook-addon-react-router-v6": "0.2.1",
"storybook-dark-mode": "1.1.2",
"storybook-dark-mode": "2.0.3",
"tailwindcss": "3.2.4",
"typescript": "4.5.5"
},

View File

@@ -0,0 +1,49 @@
const camelCase = require("lodash/camelCase");
const fs = require("fs-extra");
const upperFirst = require("lodash/upperFirst");
(async () => {
const nodeModulesPath = "@material-symbols/svg-200/rounded";
const dir = await fs.readdir("./node_modules/" + nodeModulesPath);
let generatedFile = "// @ts-nocheck\n";
const outlineIcons = {};
const solidIcons = {};
for (const file of dir) {
const fileNameParts = file.split(".");
let importName = upperFirst(camelCase(fileNameParts[0]));
if (/^\d/.test(importName)) {
importName = "_" + importName;
}
const nameAndStyleParts = fileNameParts[0].split("-");
const nameKebab = nameAndStyleParts[0].replaceAll("_", "-");
const isFillIcon =
nameAndStyleParts.length === 2 && nameAndStyleParts[1] === "fill";
if (isFillIcon) {
solidIcons[nameKebab] = {
component: importName,
};
} else {
outlineIcons[nameKebab] = {
component: importName,
};
}
generatedFile += `import { ReactComponent as ${importName} } from "${nodeModulesPath}/${file}";\n`;
}
generatedFile += "\n";
generatedFile += "const icons = {\n";
for (const [name, definition] of Object.entries(outlineIcons)) {
generatedFile += ` "${name}": ${definition.component},\n`;
generatedFile += ` "materialsymbols-outline:${name}": ${definition.component},\n`;
}
for (const [name, definition] of Object.entries(solidIcons)) {
generatedFile += ` "materialsymbols-solid:${name}": ${definition.component},\n`;
}
generatedFile += "}\n\n";
generatedFile += "export {\n";
generatedFile += " icons,\n";
generatedFile += "}";
await fs.writeFile("./src/icons/materialSymbols.ts", generatedFile);
})();

View File

@@ -1,31 +0,0 @@
import { getDashboardIconName } from "./";
describe("common.adjustMinValue", () => {
test("null returns null", () => {
expect(getDashboardIconName(null)).toEqual(null);
});
test("undefined returns null", () => {
expect(getDashboardIconName(undefined)).toEqual(null);
});
test("existing icon not mapped", () => {
expect(getDashboardIconName("bell")).toEqual("bell");
});
test("existing namespaced icon not mapped", () => {
expect(getDashboardIconName("heroicons-outline:bell")).toEqual(
"heroicons-outline:bell"
);
});
test("migrated icon mapped", () => {
expect(getDashboardIconName("search")).toEqual("magnifying-glass");
});
test("prefixed migrated icon mapped", () => {
expect(getDashboardIconName("heroicons-solid:search")).toEqual(
"heroicons-solid:magnifying-glass"
);
});
});

View File

@@ -1,179 +1,4 @@
import kebabCase from "lodash/kebabCase";
import * as outlineIconExports from "@heroicons/react/24/outline";
import * as solidIconExports from "@heroicons/react/24/solid";
const icons = {};
const migratedV2IconNames = {
adjustments: "adjustments-vertical",
annotation: "chat-bubble-bottom-center-text",
archive: "archive-box",
"arrow-circle-down": "arrow-down-circle",
"arrow-circle-left": "arrow-left-circle",
"arrow-circle-right": "arrow-right-circle",
"arrow-circle-up": "arrow-up-circle",
"arrow-narrow-down": "arrow-long-down",
"arrow-narrow-left": "arrow-long-left",
"arrow-narrow-right": "arrow-long-right",
"arrow-narrow-up": "arrow-long-up",
"arrow-sm-left": "arrow-small-left",
"arrow-sm-right": "arrow-small-right",
"arrow-sm-up": "arrow-small-up",
"arrow-sm-down": "arrow-small-down",
"arrows-expand": "arrows-pointing-out",
"badge-check": "check-badge",
ban: "no-symbol",
"bookmark-alt": "bookmark-square",
cash: "banknotes",
"chart-square-bar": "chart-bar-square",
"chat-alt-2": "chat-bubble-left-right",
"chat-alt": "chat-bubble-left-ellipsis",
chat: "chat-bubble-oval-left-ellipsis",
chip: "cpu-chip",
"clipboard-check": "clipboard-document-check",
"clipboard-copy": "clipboard-document",
"clipboard-list": "clipboard-document-list",
"cloud-download": "cloud-arrow-down",
"cloud-upload": "cloud-arrow-up",
code: "code-bracket",
collection: "rectangle-stack",
"color-swatch": "swatch",
"cursor-click": "cursor-arrow-rays",
database: "circle-stack",
"desktop-computer": "computer-desktop",
"device-mobile": "device-phone-mobile",
"document-add": "document-plus",
"document-download": "document-arrow-down",
"document-remove": "document-minus",
"document-report": "document-chart-bar",
"document-search": "document-magnifying-glass",
"dots-circle-horizontal": "ellipsis-horizontal-circle",
"dots-horizontal": "ellipsis-horizontal",
"dots-vertical": "ellipsis-vertical",
download: "arrow-down-tray",
duplicate: "square-2-stack",
"emoji-happy": "face-smile",
"emoji-sad": "face-frown",
exclamation: "exclamation-triangle",
"external-link": "arrow-top-right-on-square",
"eye-off": "eye-slash",
"fast-forward": "forward",
filter: "funnel",
"folder-add": "folder-plus",
"folder-download": "folder-arrow-down",
"folder-remove": "folder-minus",
globe: "globe-americas",
hand: "hand-raised",
"inbox-in": "inbox-arrow-down",
library: "building-library",
"lightning-bolt": "bolt",
"location-marker": "map-pin",
login: "arrow-left-on-rectangle",
logout: "arrow-right-on-rectangle",
"mail-open": "envelope-open",
mail: "envelope",
"menu-alt-1": "bars-3-center-left",
"menu-alt-2": "bars-3-bottom-left",
"menu-alt-3": "bars-3-bottom-right",
"menu-alt-4": "bars-2",
menu: "bars-3",
"minus-sm": "minus-small",
"music-note": "musical-note",
"office-building": "building-office",
"pencil-alt": "pencil-square",
"phone-incoming": "phone-arrow-down-left",
"phone-missed-call": "phone-x-mark",
"phone-outgoing": "phone-arrow-up-right",
photograph: "photo",
"plus-sm": "plus-small",
puzzle: "puzzle-piece",
qrcode: "qr-code",
"receipt-tax": "receipt-percent",
refresh: "arrow-path",
reply: "arrow-uturn-left",
rewind: "backward",
"save-as": "arrow-down-on-square-stack",
save: "arrow-down-on-square",
"search-circle": "magnifying-glass-circle",
search: "magnifying-glass",
selector: "chevron-up-down",
"sort-ascending": "bars-arrow-up",
"sort-descending": "bars-arrow-down",
speakerphone: "megaphone",
"status-offline": "signal-slash",
"status-online": "signal",
support: "lifebuoy",
"switch-horizontal": "arrow-right-left",
"switch-vertical": "arrow-up-down",
table: "table-cells",
template: "rectangle-group",
terminal: "command-line",
"thumb-down": "hand-thumb-down",
"thumb-up": "hand-thumb-up",
translate: "language",
"trending-down": "arrow-trending-down",
"trending-up": "arrow-trending-up",
upload: "arrow-up-tray",
"user-add": "user-plus",
"user-remove": "user-minus",
"view-boards": "view-columns",
"view-grid-add": "squares-plus",
"view-grid": "squares-2x2",
"view-list": "bars-4",
"volume-off": "speaker-x-mark",
"volume-up": "speaker-wave",
x: "x-mark",
"zoom-in": "magnifying-glass-plus",
"zoom-out": "magnifying-glass-minus",
};
const kebabCaseExceptions = {
Square3Stack3D: "square-3-stack-3d",
Squares2X2: "squares-2x2",
};
const convertIconName = (name) => {
let condensedName = name;
const iconOccurrence = name.lastIndexOf("Icon");
if (iconOccurrence >= 0) {
condensedName = condensedName.substring(0, iconOccurrence);
}
return kebabCaseExceptions[condensedName] || kebabCase(condensedName);
};
const getDashboardIconName = (name?: string | null) => {
if (!name) {
return null;
}
const parts = name.split(":");
if (parts.length === 1) {
const migratedV2IconName = migratedV2IconNames[parts[0]];
if (migratedV2IconName) {
return migratedV2IconName;
}
return name;
} else if (parts.length === 2) {
const migratedV2IconName = migratedV2IconNames[parts[1]];
if (migratedV2IconName) {
return [parts[0], migratedV2IconName].join(":");
}
return name;
} else {
return null;
}
};
Object.entries(outlineIconExports).forEach(([name, exported]) => {
const iconName = convertIconName(name);
icons[iconName] = exported;
icons[`heroicons-outline:${iconName}`] = exported;
});
Object.entries(solidIconExports).forEach(([name, exported]) => {
const iconName = convertIconName(name);
icons[`heroicons-solid:${iconName}`] = exported;
});
import useDashboardIcons from "../../hooks/useDashboardIcons";
interface IconProps {
className?: string;
@@ -183,13 +8,29 @@ interface IconProps {
}
const Icon = ({ className = "h-6 w-6", icon, style, title }: IconProps) => {
const MatchingIcon = icons[getDashboardIconName(icon)];
const icons = useDashboardIcons();
let MatchingIcon = icons.materialSymbols[icon];
if (MatchingIcon) {
return (
<MatchingIcon
className={className}
style={{
fill: "currentColor",
color: style ? style.color : undefined,
}}
title={title}
/>
);
} else {
MatchingIcon = icons.heroIcons[icon];
}
if (!MatchingIcon) {
return null;
}
return <MatchingIcon className={className} style={style} title={title} />;
};
export default Icon;
export { getDashboardIconName };

View File

@@ -36,7 +36,7 @@ const SaveSnapshotButton = () => {
<>
<Icon
className="inline-block text-foreground-lighter w-5 -mt-0.5"
icon="camera"
icon="heroicons-outline:camera"
/>
<span className="hidden lg:block">Snap</span>
</>

View File

@@ -1,4 +1,5 @@
import DashboardIcon from "../dashboards/common/DashboardIcon";
import { classNames } from "../../utils/styles";
import { ThemeNames } from "../../hooks/useTheme";
import { useDashboard } from "../../hooks/useDashboard";
@@ -9,12 +10,12 @@ const ThemeToggle = () => {
return (
<button
type="button"
className={
(theme.name === ThemeNames.STEAMPIPE_DEFAULT
className={classNames(
theme.name === ThemeNames.STEAMPIPE_DEFAULT
? "bg-gray-200"
: "bg-gray-500") +
" relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-1 focus:ring-offset-2 focus:ring-indigo-500"
}
: "bg-gray-500",
"relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-1 focus:ring-offset-2 focus:ring-indigo-500"
)}
onClick={() =>
setTheme(
theme.name === ThemeNames.STEAMPIPE_DEFAULT
@@ -26,34 +27,40 @@ const ThemeToggle = () => {
>
<span className="sr-only">Use setting</span>
<span
className={
(theme.name === ThemeNames.STEAMPIPE_DEFAULT
className={classNames(
theme.name === ThemeNames.STEAMPIPE_DEFAULT
? "translate-x-0"
: "translate-x-5") +
" pointer-events-none relative inline-block h-5 w-5 rounded-full bg-dashboard-panel shadow transform ring-0 transition ease-in-out duration-200"
}
: "translate-x-5",
"pointer-events-none relative inline-block h-5 w-5 rounded-full bg-dashboard-panel shadow transform ring-0 transition ease-in-out duration-200"
)}
>
<span
className={
(theme.name === ThemeNames.STEAMPIPE_DEFAULT
className={classNames(
theme.name === ThemeNames.STEAMPIPE_DEFAULT
? "opacity-100 ease-in duration-200"
: "opacity-0 ease-out duration-100") +
" absolute inset-0 h-full w-full flex items-center justify-center transition-opacity text-gray-500"
}
: "opacity-0 ease-out duration-100",
"absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
)}
aria-hidden="true"
>
<DashboardIcon icon="heroicons-solid:sun" />
<DashboardIcon
className="text-gray-500"
icon="materialsymbols-solid:light-mode"
/>
</span>
<span
className={
(theme.name === ThemeNames.STEAMPIPE_DEFAULT
className={classNames(
theme.name === ThemeNames.STEAMPIPE_DEFAULT
? "opacity-0 ease-out duration-100"
: "opacity-100 ease-in duration-200") +
" absolute inset-0 h-full w-full flex items-center justify-center transition-opacity text-gray-500"
}
: "opacity-100 ease-in duration-200",
"absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
)}
aria-hidden="true"
>
<DashboardIcon icon="heroicons-solid:moon" />
<DashboardIcon
className="text-gray-500"
icon="materialsymbols-solid:dark-mode"
/>
</span>
</span>
</button>

View File

@@ -27,40 +27,43 @@ LoadingCustomIcon.args = {
export const LoadingOK = Template.bind({});
LoadingOK.args = {
data: null,
properties: { type: "ok" },
display_type: "ok",
};
export const LoadingOKCustomIcon = Template.bind({});
LoadingOKCustomIcon.storyName = "Loading OK (Custom Icon)";
LoadingOKCustomIcon.args = {
data: null,
properties: { type: "ok", icon: "check-circle" },
display_type: "ok",
properties: { icon: "check-circle" },
};
export const LoadingAlert = Template.bind({});
LoadingAlert.args = {
data: null,
properties: { type: "alert" },
display_type: "alert",
};
export const LoadingAlertCustomIcon = Template.bind({});
LoadingAlertCustomIcon.storyName = "Loading Alert (Custom Icon)";
LoadingAlertCustomIcon.args = {
data: null,
properties: { type: "alert", icon: "shield-exclamation" },
display_type: "alert",
properties: { icon: "shield-exclamation" },
};
export const LoadingInfo = Template.bind({});
LoadingInfo.args = {
data: null,
properties: { type: "info" },
display_type: "info",
};
export const LoadingInfoCustomIcon = Template.bind({});
LoadingInfoCustomIcon.storyName = "Loading Info (Custom Icon)";
LoadingInfoCustomIcon.args = {
data: null,
properties: { type: "info", icon: "light-bulb" },
display_type: "info",
properties: { icon: "light-bulb" },
};
export const Error = Template.bind({});
@@ -77,19 +80,19 @@ Empty.args = {
export const EmptyOK = Template.bind({});
EmptyOK.args = {
data: [],
properties: { type: "ok" },
display_type: "ok",
};
export const EmptyAlert = Template.bind({});
EmptyAlert.args = {
data: [],
properties: { type: "alert" },
display_type: "alert",
};
export const EmptyInfo = Template.bind({});
EmptyInfo.args = {
data: [],
properties: { type: "info" },
display_type: "info",
};
export const StringValue = Template.bind({});
@@ -122,7 +125,7 @@ SimpleDataFormatOK.args = {
columns: [{ name: "Encrypted EC2 Instances", data_type: "INT8" }],
rows: [{ "Encrypted EC2 Instances": 5 }],
},
properties: { type: "ok" },
display_type: "ok",
};
export const SimpleDataFormatOKCustomIcon = Template.bind({});
@@ -132,7 +135,8 @@ SimpleDataFormatOKCustomIcon.args = {
columns: [{ name: "Encrypted EC2 Instances", data_type: "INT8" }],
rows: [{ "Encrypted EC2 Instances": 5 }],
},
properties: { type: "ok", icon: "check-circle" },
display_type: "ok",
properties: { icon: "check-circle" },
};
export const SimpleDataFormatAlert = Template.bind({});
@@ -141,7 +145,7 @@ SimpleDataFormatAlert.args = {
columns: [{ name: "Public Buckets", data_type: "INT8" }],
rows: [{ "Public Buckets": 5 }],
},
properties: { type: "alert" },
display_type: "alert",
};
export const SimpleDataFormatAlertCustomIcon = Template.bind({});
@@ -152,7 +156,8 @@ SimpleDataFormatAlertCustomIcon.args = {
columns: [{ name: "Public Buckets", data_type: "INT8" }],
rows: [{ "Public Buckets": 5 }],
},
properties: { type: "alert", icon: "shield-exclamation" },
display_type: "alert",
properties: { icon: "shield-exclamation" },
};
export const SimpleDataFormatInfo = Template.bind({});
@@ -161,7 +166,7 @@ SimpleDataFormatInfo.args = {
columns: [{ name: "EC2 Instances", data_type: "INT8" }],
rows: [{ "EC2 Instances": 106 }],
},
properties: { type: "info" },
display_type: "info",
};
export const SimpleDataFormatInfoCustomIcon = Template.bind({});
@@ -172,7 +177,8 @@ SimpleDataFormatInfoCustomIcon.args = {
columns: [{ name: "EC2 Instances", data_type: "INT8" }],
rows: [{ "EC2 Instances": 106 }],
},
properties: { type: "info", icon: "light-bulb" },
display_type: "info",
properties: { icon: "light-bulb" },
};
export const SimpleDataFormatThousands = Template.bind({});
@@ -182,7 +188,7 @@ SimpleDataFormatThousands.args = {
columns: [{ name: "EC2 Instances", data_type: "INT8" }],
rows: [{ "EC2 Instances": 1236 }],
},
properties: { type: "info" },
display_type: "info",
};
export const SimpleDataFormatMillions = Template.bind({});
@@ -192,7 +198,7 @@ SimpleDataFormatMillions.args = {
columns: [{ name: "Log Lines", data_type: "INT8" }],
rows: [{ "Log Lines": 5236174 }],
},
properties: { type: "info" },
display_type: "info",
};
export const FormalDataFormat = Template.bind({});

View File

@@ -16,6 +16,7 @@ import { DashboardDataModeLive } from "../../../types";
import { getComponent, registerComponent } from "../index";
import {
getIconClasses,
getIconForType,
getTextClasses,
getWrapperClasses,
} from "../../../utils/card";
@@ -58,29 +59,6 @@ const getDataFormat = (data: LeafNodeData): CardDataFormat => {
return "simple";
};
const getIconForType = (type, icon) => {
if (!type && !icon) {
return null;
}
if (icon) {
return icon;
}
switch (type) {
case "alert":
return "heroicons-solid:exclamation-circle";
case "ok":
return "heroicons-solid:check-circle";
case "info":
return "heroicons-solid:information-circle";
case "severity":
return "heroicons-solid:exclamation";
default:
return null;
}
};
const useCardState = ({ data, sql, display_type, properties }: CardProps) => {
const [calculatedProperties, setCalculatedProperties] = useState<CardState>({
loading: !!sql,
@@ -233,12 +211,6 @@ const Card = (props: CardProps) => {
doRender();
}, [state, props.data]);
// return (
// <div className="bg-alert text-alert print:bg-white print:border-2 print:border-alert">
// Hello
// </div>
// );
const card = (
<div
className={classNames(
@@ -263,7 +235,10 @@ const Card = (props: CardProps) => {
>
{state.loading && "Loading..."}
{!state.loading && !state.label && (
<DashboardIcon className="h-5 w-5" icon="heroicons-solid:minus" />
<DashboardIcon
className="h-5 w-5"
icon="materialsymbols-outline:remove"
/>
)}
{!state.loading && state.label}
</p>
@@ -295,7 +270,7 @@ const Card = (props: CardProps) => {
(state.value === null || state.value === undefined) && (
<DashboardIcon
className="h-10 w-10"
icon="heroicons-solid:minus"
icon="materialsymbols-outline:remove"
/>
)}
{state.value !== null &&

View File

@@ -90,7 +90,7 @@ const Benchmark = (props: InnerCheckProps) => {
properties: {
label: "OK",
value: totalSummary.ok.toString(),
icon: "heroicons-solid:check-circle",
icon: "materialsymbols-solid:check-circle",
},
},
{
@@ -100,7 +100,7 @@ const Benchmark = (props: InnerCheckProps) => {
properties: {
label: "Alarm",
value: totalSummary.alarm.toString(),
icon: "heroicons-solid:bell",
icon: "materialsymbols-solid:notifications",
},
},
{
@@ -110,7 +110,7 @@ const Benchmark = (props: InnerCheckProps) => {
properties: {
label: "Error",
value: totalSummary.error.toString(),
icon: "heroicons-solid:exclamation-circle",
icon: "materialsymbols-solid:error",
},
},
{
@@ -120,7 +120,7 @@ const Benchmark = (props: InnerCheckProps) => {
properties: {
label: "Info",
value: totalSummary.info.toString(),
icon: "heroicons-solid:information-circle",
icon: "materialsymbols-solid:info",
},
},
{
@@ -129,7 +129,7 @@ const Benchmark = (props: InnerCheckProps) => {
properties: {
label: "Skipped",
value: totalSummary.skip.toString(),
icon: "heroicons-solid:arrow-circle-right",
icon: "materialsymbols-solid:arrow-circle-right",
},
},
];
@@ -150,7 +150,7 @@ const Benchmark = (props: InnerCheckProps) => {
properties: {
label: "Critical / High",
value: total.toString(),
icon: "heroicons-solid:exclamation",
icon: "materialsymbols-solid:exclamation",
},
});
}

View File

@@ -18,13 +18,11 @@ const CheckGrouping = ({ node }: CheckGroupingProps) => {
useState<CheckGroupNodeStates | null>(null);
const expand = useCallback(() => {
// console.log("Capturing and expanding", nodeStates);
setRestoreNodeStates(nodeStates);
dispatch({ type: CheckGroupingActions.EXPAND_ALL_NODES });
}, [dispatch, nodeStates]);
const restore = useCallback(() => {
// console.log("Restoring", restoreNodeStates);
if (restoreNodeStates) {
dispatch({
type: CheckGroupingActions.UPDATE_NODES,

View File

@@ -111,118 +111,6 @@ const ProgressBarGroup = ({ children, className }: ProgressBarGroupProps) => (
</div>
);
// interface ValueWithIndex {
// value: number;
// percent: number;
// index: number;
// }
// const ensureMinPercentages = (
// name,
// values: number[] = [],
// minPercentage = 2
// ) => {
// // Summary here is I want to ensure each percent is >= 2% and a round number, so I'll adjust
// // all other values accordingly to ensure we total 100%
// const total = values.reduce((partial, v) => partial + v, 0);
// const valuesWithPercentAndIndex: ValueWithIndex[] = [];
// for (let i = 0; i < values.length; i++) {
// const value = values[i];
// valuesWithPercentAndIndex.push({
// value,
// percent: (value / total) * 100,
// index: i,
// });
// }
// const withMinPercentages = valuesWithPercentAndIndex.map((p) => ({
// ...p,
// percent:
// p.percent > 0 && p.percent < minPercentage ? minPercentage : p.percent,
// }));
// const flooredPercentages = withMinPercentages.map((p) => ({
// ...p,
// percent: p.percent > 0 ? Math.floor(p.percent) : p.percent,
// }));
// let diff =
// flooredPercentages.reduce((partial, v) => partial + v.percent, 0) - 100;
// const numberOfValuesToDistributeAcross = flooredPercentages.filter((p) => {
// if (diff < 0) {
// return p.percent > minPercentage && 100 - p.percent + 4 > 0;
// } else {
// return p.percent > minPercentage && p.percent - 4 > minPercentage;
// }
// }).length;
// const perItem = diff / numberOfValuesToDistributeAcross;
// // if (name === "aws_compliance.control.cis_v140_1_12") {
// // console.log({
// // values,
// // total,
// // valuesWithPercentAndIndex,
// // withMinPercentages,
// // flooredPercentages,
// // numberOfValuesToDistributeAcross,
// // perItem,
// // diff,
// // });
// // }
// let adjusted;
// if (diff < 0) {
// const ascending = [...flooredPercentages]
// .sort((a, b) =>
// a.percent < b.percent ? -1 : a.percent > b.percent ? 1 : 0
// )
// .map((p) => ({ ...p }));
// for (const percentageItem of ascending) {
// if (
// diff === 0 ||
// percentageItem.percent < minPercentage ||
// percentageItem.percent - 4 <= minPercentage
// ) {
// continue;
// }
// if (perItem < 0 && perItem > -1) {
// percentageItem.percent += 1;
// diff += 1;
// } else {
// percentageItem.percent -= perItem;
// diff -= perItem;
// }
// }
// adjusted = ascending
// .sort((a, b) => (a.index < b.index ? -1 : a.index > b.index ? 1 : 0))
// .map((p) => p.percent);
// } else {
// const descending = [...flooredPercentages]
// .sort((a, b) =>
// b.percent < a.percent ? -1 : b.percent > a.percent ? 1 : 0
// )
// .map((p) => ({ ...p }));
// for (const percentageItem of descending) {
// if (
// diff === 0 ||
// percentageItem.percent < minPercentage ||
// percentageItem.percent - 4 <= minPercentage
// ) {
// continue;
// }
// if (perItem > 0 && perItem < 1) {
// percentageItem.percent -= 1;
// diff -= 1;
// } else {
// percentageItem.percent -= perItem;
// diff -= perItem;
// }
// }
// adjusted = descending
// .sort((a, b) => (a.index < b.index ? -1 : a.index > b.index ? 1 : 0))
// .map((p) => p.percent);
// }
// // if (name === "aws_compliance.control.cis_v140_1_12") {
// // console.log(adjusted);
// // }
// return adjusted;
// };
const ProgressBar = ({ className, percent }: ProgressBarProps) => {
if (!percent) {
return null;

View File

@@ -17,16 +17,67 @@ heroIconDefaultOutline.args = {
icon: "arrow-up-circle",
};
export const heroIconDefaultOutlineWithColor = Template.bind({});
heroIconDefaultOutlineWithColor.args = {
icon: "arrow-up-circle",
style: { color: "red" },
};
export const heroIconOutlineFullyNamespaced = Template.bind({});
heroIconOutlineFullyNamespaced.args = {
icon: "heroicons-outline:arrow-up-circle",
};
export const heroIconOutlineFullyNamespacedWithColor = Template.bind({});
heroIconOutlineFullyNamespacedWithColor.args = {
icon: "heroicons-outline:arrow-up-circle",
style: { color: "red" },
};
export const heroIconSolid = Template.bind({});
heroIconSolid.args = {
icon: "heroicons-solid:arrow-up-circle",
};
export const heroIconSolidWithColor = Template.bind({});
heroIconSolidWithColor.args = {
icon: "heroicons-solid:arrow-up-circle",
style: { color: "red" },
};
export const materialSymbolDefaultOutline = Template.bind({});
materialSymbolDefaultOutline.args = {
icon: "cloud",
};
export const materialSymbolDefaultOutlineWithColor = Template.bind({});
materialSymbolDefaultOutlineWithColor.args = {
icon: "cloud",
style: { color: "red" },
};
export const materialSymbolOutlineFullyNamespaced = Template.bind({});
materialSymbolOutlineFullyNamespaced.args = {
icon: "materialsymbols-outline:cloud",
};
export const materialSymbolOutlineFullyNamespacedWithColor = Template.bind({});
materialSymbolOutlineFullyNamespacedWithColor.args = {
icon: "materialsymbols-outline:cloud",
style: { color: "red" },
};
export const materialSymbolSolid = Template.bind({});
materialSymbolSolid.args = {
icon: "materialsymbols-solid:cloud",
};
export const materialSymbolSolidWithColor = Template.bind({});
materialSymbolSolidWithColor.args = {
icon: "materialsymbols-solid:cloud",
style: { color: "red" },
};
export const text1Letter = Template.bind({});
text1Letter.args = {
icon: "text:A",

View File

@@ -1,6 +1,6 @@
import Icon from "../../Icon";
import { classNames } from "../../../utils/styles";
import { memo, useMemo } from "react";
import { useMemo } from "react";
interface DashboardIconProps {
className?: string;
@@ -9,14 +9,12 @@ interface DashboardIconProps {
title?: string;
}
interface DashboardHeroIconProps extends DashboardIconProps {
interface DashboardTextIconProps extends DashboardIconProps {
icon: string;
style?: any;
}
interface DashboardImageIconProps extends DashboardIconProps {
icon: string;
style?: any;
}
const useDashboardIconType = (icon) =>
@@ -28,7 +26,9 @@ const useDashboardIconType = (icon) =>
// This gets parsed as a URL if we don't check first
if (
icon.startsWith("heroicons-outline:") ||
icon.startsWith("heroicons-solid:")
icon.startsWith("heroicons-solid:") ||
icon.startsWith("materialsymbols-outline:") ||
icon.startsWith("materialsymbols-solid:")
) {
return "icon";
}
@@ -57,21 +57,12 @@ const DashboardImageIcon = ({
<img className={className} src={icon} alt="" style={style} title={title} />
);
const DashboardHeroIcon = ({
className,
icon,
style,
title,
}: DashboardHeroIconProps) => (
<Icon className={className} icon={icon} style={style} title={title} />
);
const DashboardTextIcon = ({
className,
icon,
style,
title,
}: DashboardHeroIconProps) => {
}: DashboardTextIconProps) => {
const text = useMemo(() => {
if (!icon) {
return "";
@@ -104,12 +95,7 @@ const DashboardIcon = ({
switch (iconType) {
case "icon":
return (
<DashboardHeroIcon
className={className}
icon={icon}
style={style}
title={title}
/>
<Icon className={className} icon={icon} style={style} title={title} />
);
case "text":
return (
@@ -134,6 +120,6 @@ const DashboardIcon = ({
}
};
export default memo(DashboardIcon);
export default DashboardIcon;
export { useDashboardIconType };

View File

@@ -51,7 +51,7 @@ const NodeAndEdgePanelInformation = ({
<div className="flex items-center space-x-1">
<Icon
className="w-3.5 h-3.5 text-alert"
icon="heroicons-solid:exclamation-circle"
icon="materialsymbols-solid:error"
/>
<span key={category.id} className="block">
{category.title || category.id}

View File

@@ -0,0 +1,22 @@
import { useMemo } from "react";
let heroIcons = {};
let materialSymbols = {};
import("../icons/heroIcons").then((m) => {
heroIcons = m.icons;
});
import("../icons/materialSymbols").then((m) => {
materialSymbols = m.icons;
});
const useDashboardIcons = () => {
return useMemo(
() => ({
heroIcons,
materialSymbols,
}),
[heroIcons, materialSymbols]
);
};
export default useDashboardIcons;

View File

@@ -0,0 +1,23 @@
import { icons } from "./heroIcons";
describe("hero icons", () => {
test("unknown returns null", () => {
expect(icons["hjdfghskhgskfdjg"]).toEqual(undefined);
});
test("existing icon not mapped", () => {
expect(icons["bell"]).not.toBeNull();
});
test("existing namespaced icon not mapped", () => {
expect(icons["heroicons-outline:bell"]).not.toBeNull();
});
test("migrated icon mapped", () => {
expect(icons["search"]).not.toBeNull();
});
test("prefixed migrated icon mapped", () => {
expect(icons["heroicons-solid:search"]).not.toBeNull();
});
});

View File

@@ -0,0 +1,162 @@
import kebabCase from "lodash/kebabCase";
import * as outline from "@heroicons/react/24/outline";
import * as solid from "@heroicons/react/24/solid";
const migratedV2IconNames = {
adjustments: "adjustments-vertical",
annotation: "chat-bubble-bottom-center-text",
archive: "archive-box",
"arrow-circle-down": "arrow-down-circle",
"arrow-circle-left": "arrow-left-circle",
"arrow-circle-right": "arrow-right-circle",
"arrow-circle-up": "arrow-up-circle",
"arrow-narrow-down": "arrow-long-down",
"arrow-narrow-left": "arrow-long-left",
"arrow-narrow-right": "arrow-long-right",
"arrow-narrow-up": "arrow-long-up",
"arrow-sm-left": "arrow-small-left",
"arrow-sm-right": "arrow-small-right",
"arrow-sm-up": "arrow-small-up",
"arrow-sm-down": "arrow-small-down",
"arrows-expand": "arrows-pointing-out",
"badge-check": "check-badge",
ban: "no-symbol",
"bookmark-alt": "bookmark-square",
cash: "banknotes",
"chart-square-bar": "chart-bar-square",
"chat-alt-2": "chat-bubble-left-right",
"chat-alt": "chat-bubble-left-ellipsis",
chat: "chat-bubble-oval-left-ellipsis",
chip: "cpu-chip",
"clipboard-check": "clipboard-document-check",
"clipboard-copy": "clipboard-document",
"clipboard-list": "clipboard-document-list",
"cloud-download": "cloud-arrow-down",
"cloud-upload": "cloud-arrow-up",
code: "code-bracket",
collection: "rectangle-stack",
"color-swatch": "swatch",
"cursor-click": "cursor-arrow-rays",
database: "circle-stack",
"desktop-computer": "computer-desktop",
"device-mobile": "device-phone-mobile",
"document-add": "document-plus",
"document-download": "document-arrow-down",
"document-remove": "document-minus",
"document-report": "document-chart-bar",
"document-search": "document-magnifying-glass",
"dots-circle-horizontal": "ellipsis-horizontal-circle",
"dots-horizontal": "ellipsis-horizontal",
"dots-vertical": "ellipsis-vertical",
download: "arrow-down-tray",
duplicate: "square-2-stack",
"emoji-happy": "face-smile",
"emoji-sad": "face-frown",
exclamation: "exclamation-triangle",
"external-link": "arrow-top-right-on-square",
"eye-off": "eye-slash",
"fast-forward": "forward",
filter: "funnel",
"folder-add": "folder-plus",
"folder-download": "folder-arrow-down",
"folder-remove": "folder-minus",
globe: "globe-americas",
hand: "hand-raised",
"inbox-in": "inbox-arrow-down",
library: "building-library",
"lightning-bolt": "bolt",
"location-marker": "map-pin",
login: "arrow-left-on-rectangle",
logout: "arrow-right-on-rectangle",
"mail-open": "envelope-open",
mail: "envelope",
"menu-alt-1": "bars-3-center-left",
"menu-alt-2": "bars-3-bottom-left",
"menu-alt-3": "bars-3-bottom-right",
"menu-alt-4": "bars-2",
menu: "bars-3",
"minus-sm": "minus-small",
"music-note": "musical-note",
"office-building": "building-office",
"pencil-alt": "pencil-square",
"phone-incoming": "phone-arrow-down-left",
"phone-missed-call": "phone-x-mark",
"phone-outgoing": "phone-arrow-up-right",
photograph: "photo",
"plus-sm": "plus-small",
puzzle: "puzzle-piece",
qrcode: "qr-code",
"receipt-tax": "receipt-percent",
refresh: "arrow-path",
reply: "arrow-uturn-left",
rewind: "backward",
"save-as": "arrow-down-on-square-stack",
save: "arrow-down-on-square",
"search-circle": "magnifying-glass-circle",
search: "magnifying-glass",
selector: "chevron-up-down",
"sort-ascending": "bars-arrow-up",
"sort-descending": "bars-arrow-down",
speakerphone: "megaphone",
"status-offline": "signal-slash",
"status-online": "signal",
support: "lifebuoy",
"switch-horizontal": "arrow-right-left",
"switch-vertical": "arrow-up-down",
table: "table-cells",
template: "rectangle-group",
terminal: "command-line",
"thumb-down": "hand-thumb-down",
"thumb-up": "hand-thumb-up",
translate: "language",
"trending-down": "arrow-trending-down",
"trending-up": "arrow-trending-up",
upload: "arrow-up-tray",
"user-add": "user-plus",
"user-remove": "user-minus",
"view-boards": "view-columns",
"view-grid-add": "squares-plus",
"view-grid": "squares-2x2",
"view-list": "bars-4",
"volume-off": "speaker-x-mark",
"volume-up": "speaker-wave",
x: "x-mark",
"zoom-in": "magnifying-glass-plus",
"zoom-out": "magnifying-glass-minus",
};
const kebabCaseExceptions = {
Square3Stack3D: "square-3-stack-3d",
Squares2X2: "squares-2x2",
};
const convertIconName = (name) => {
let condensedName = name;
const iconOccurrence = name.lastIndexOf("Icon");
if (iconOccurrence >= 0) {
condensedName = condensedName.substring(0, iconOccurrence);
}
return kebabCaseExceptions[condensedName] || kebabCase(condensedName);
};
const icons = {};
Object.entries(outline).forEach(([name, exported]) => {
const iconName = convertIconName(name);
icons[iconName] = exported;
icons[`heroicons-outline:${iconName}`] = exported;
});
Object.entries(solid).forEach(([name, exported]) => {
const iconName = convertIconName(name);
icons[`heroicons-solid:${iconName}`] = exported;
});
Object.entries(migratedV2IconNames).forEach(([name, mappedName]) => {
const mappedIcon = icons[mappedName];
icons[name] = mappedIcon;
icons[`heroicons-outline:${name}`] = mappedIcon;
icons[`heroicons-solid:${name}`] = mappedIcon;
});
export { icons };

View File

@@ -16,6 +16,29 @@ const getIconClasses = (type) => {
}
};
const getIconForType = (type, icon) => {
if (!type && !icon) {
return null;
}
if (icon) {
return icon;
}
switch (type) {
case "alert":
return "materialsymbols-solid:error";
case "ok":
return "materialsymbols-solid:check-circle";
case "info":
return "materialsymbols-solid:info";
case "severity":
return "materialsymbols-solid:exclamation";
default:
return null;
}
};
const getTextClasses = (type) => {
switch (type) {
case "alert":
@@ -46,4 +69,4 @@ const getWrapperClasses = (type) => {
}
};
export { getIconClasses, getTextClasses, getWrapperClasses };
export { getIconClasses, getIconForType, getTextClasses, getWrapperClasses };

File diff suppressed because it is too large Load Diff