From 848ae2585e96cb5fca52cf61ac770c8dbd9c8c29 Mon Sep 17 00:00:00 2001
From: Alissa Cheng <cheng@astron.nl>
Date: Wed, 27 Mar 2024 08:43:22 +0000
Subject: [PATCH] feat!: make icons great again

BREAKING CHANGE: changed icon names to PascalCase
---
 .eslintrc.cjs                  |  1 +
 README.md                      | 11 +++++--
 src/assets/icons/index.ts      | 59 +++++++++++-----------------------
 src/components/Alert.tsx       | 10 +++---
 src/components/Breadcrumb.tsx  |  2 +-
 src/components/Checkbox.tsx    |  2 +-
 src/components/DateInput.tsx   |  6 ++--
 src/components/Dropdown.tsx    |  2 +-
 src/components/Icon.tsx        | 51 +++--------------------------
 src/components/Menu.tsx        |  4 +--
 src/components/Modal.tsx       |  2 +-
 src/stories/Button.stories.tsx |  2 +-
 src/stories/Icon.stories.ts    |  5 +--
 src/stories/Menu.stories.tsx   |  6 ++--
 14 files changed, 53 insertions(+), 110 deletions(-)

diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 738aab6..5a0fa6c 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -42,6 +42,7 @@ module.exports = {
       "warn",
       { allowConstantExport: true },
     ],
+    "import/namespace": ["error", { allowComputed: true }],
   },
   settings: {
     "import/parsers": {
diff --git a/README.md b/README.md
index 9466338..2f63c71 100644
--- a/README.md
+++ b/README.md
@@ -68,11 +68,16 @@ This is the current workflow:
 
 Download or copy the SVG code from Figma. Save it into `src/assets/icons/your-file-name.svg`.
 In the file, change the stroke color to `currentColor`. This is to enable dynamic stroke colors.
-Add an entry to the `intex.ts` where you import the svg file, since this allows the bundler to pick-up the file correctly.
-Add `"your-file-name"` to `iconNameOptions` array and the `iconMap` dict of `Icon.tsx`. Then, you can use your icon like this:
+Add an _export_ entry to the `src/assets/icons/index.ts`:
 
 ```tsx
-<Icon name="your-file-name" color="secondary" />
+export { default as YourFileName } from "./your-file-name.svg";
+```
+
+Then, you can use your icon like this (note the casing):
+
+```tsx
+<Icon name="YourFileName" color="secondary" />
 ```
 
 ### Classnames
diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts
index 7151d96..34be31e 100644
--- a/src/assets/icons/index.ts
+++ b/src/assets/icons/index.ts
@@ -1,41 +1,20 @@
-import AttentionCircle from "./attention-circle.svg";
-import CheckmarkCircle from "./checkmark-circle.svg";
-import Checkmark from "./checkmark.svg";
-import CrossCircle from "./cross-circle.svg";
-import Cross from "./cross.svg";
-import Datepicker from "./datepicker.svg";
-import Download from "./download.svg";
-import Dropdown from "./dropdown.svg";
-import Edit from "./edit.svg";
-import Expand from "./expand.svg";
-import Eye from "./eye.svg";
-import InfoCircle from "./info-circle.svg";
-import Layer from "./layer.svg";
-import Magnifier from "./magnifier.svg";
-import Minus from "./minus.svg";
-import Next from "./next.svg";
-import Plus from "./plus.svg";
-import Previous from "./previous.svg";
-import Up from "./up.svg";
+export { default as AttentionCircle } from "./attention-circle.svg";
 
-export default {
-  AttentionCircle,
-  CheckmarkCircle,
-  Checkmark,
-  CrossCircle,
-  Cross,
-  Datepicker,
-  Download,
-  Dropdown,
-  Edit,
-  Expand,
-  Eye,
-  InfoCircle,
-  Layer,
-  Magnifier,
-  Minus,
-  Next,
-  Plus,
-  Previous,
-  Up,
-};
+export { default as CheckmarkCircle } from "./checkmark-circle.svg";
+export { default as Checkmark } from "./checkmark.svg";
+export { default as CrossCircle } from "./cross-circle.svg";
+export { default as Cross } from "./cross.svg";
+export { default as Datepicker } from "./datepicker.svg";
+export { default as Download } from "./download.svg";
+export { default as Dropdown } from "./dropdown.svg";
+export { default as Edit } from "./edit.svg";
+export { default as Expand } from "./expand.svg";
+export { default as Eye } from "./eye.svg";
+export { default as InfoCircle } from "./info-circle.svg";
+export { default as Layer } from "./layer.svg";
+export { default as Magnifier } from "./magnifier.svg";
+export { default as Minus } from "./minus.svg";
+export { default as Next } from "./next.svg";
+export { default as Plus } from "./plus.svg";
+export { default as Previous } from "./previous.svg";
+export { default as Up } from "./up.svg";
diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx
index b5a3bc1..b4a339d 100644
--- a/src/components/Alert.tsx
+++ b/src/components/Alert.tsx
@@ -20,10 +20,10 @@ type AlertProps = {
  */
 const Alert = ({ title, message, alertType = "primary" }: AlertProps) => {
   const icon = {
-    primary: "info-circle",
-    warning: "attention-circle",
-    positive: "checkmark-circle",
-    negative: "cross-circle",
+    primary: "InfoCircle",
+    warning: "AttentionCircle",
+    positive: "CheckmarkCircle",
+    negative: "CrossCircle",
   };
   const baseClass =
     "flex flex-row items-start py-[13px] px-[15px] rounded-[8px] w-min border-l-8 border-y-0 border-r-0 bg-baseWhite dark:bg-mediumGrey shadow-2xl";
@@ -40,7 +40,7 @@ const Alert = ({ title, message, alertType = "primary" }: AlertProps) => {
         <Typography text={message} variant="paragraph" />
       </div>
       {/* if used more often than below and in Modal, consider making it a CloseIcon */}
-      <Icon name="cross" color="body" customClass="cursor-pointer" />
+      <Icon name="Cross" color="body" customClass="cursor-pointer" />
     </div>
   );
 };
diff --git a/src/components/Breadcrumb.tsx b/src/components/Breadcrumb.tsx
index 6528138..36e243a 100644
--- a/src/components/Breadcrumb.tsx
+++ b/src/components/Breadcrumb.tsx
@@ -17,7 +17,7 @@ const Breadcrumb = ({ items }: BreadcrumbProps) => {
       itemsBeforeCollapse={1}
       itemsAfterCollapse={2}
       separator={
-        <Icon name="next" color="body" customClass="w-[7px] h-[7px]" />
+        <Icon name="Next" color="body" customClass="w-[7px] h-[7px]" />
       }
       itemClasses={{
         item: clsx(
diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx
index 78f3505..b687b53 100644
--- a/src/components/Checkbox.tsx
+++ b/src/components/Checkbox.tsx
@@ -30,7 +30,7 @@ const Checkbox = ({
       isDisabled={isDisabled}
       isSelected={isSelected}
       onValueChange={setIsSelected}
-      icon={isSelected ? <Icon name="checkmark" color="body" /> : <></>}
+      icon={isSelected ? <Icon name="Checkmark" color="body" /> : <></>}
       // This seems to be the only way to enable customization of background and border color
       // that's why the color "default" is set to "transparent" in tailwind.config.js
       color="default"
diff --git a/src/components/DateInput.tsx b/src/components/DateInput.tsx
index 024a44d..8c8acb3 100644
--- a/src/components/DateInput.tsx
+++ b/src/components/DateInput.tsx
@@ -77,7 +77,7 @@ const DateInput = ({
             labelCustomColor={labelCustomColor}
             endContent={
               <Icon
-                name="datepicker"
+                name="Datepicker"
                 // Note: textColor("body") has a different grey, hence the below is used
                 customClass="text-grey dark:text-baseWhite"
               />
@@ -106,7 +106,7 @@ const DateInput = ({
   }: PaginationProps) => (
     <div className="flex flex-row justify-between items-center px-2 h-[30px] border-b-1 border-grey">
       <button onClick={onClickPrev} className="w-[16px] h-[16px]">
-        <Icon name="previous" color="body" />
+        <Icon name="Previous" color="body" />
       </button>
       <Typography
         text={text}
@@ -115,7 +115,7 @@ const DateInput = ({
         customClass={textColor("body")}
       />
       <button onClick={onClickNext} className="w-[16px] h-[16px]">
-        <Icon name="next" color="body" />
+        <Icon name="Next" color="body" />
       </button>
     </div>
   );
diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx
index b1362f0..df16f8e 100644
--- a/src/components/Dropdown.tsx
+++ b/src/components/Dropdown.tsx
@@ -58,7 +58,7 @@ const Dropdown = ({
         variant: "bordered",
         isRequired: isRequired,
       }} // use the same props as BaseInput
-      selectorIcon={<Icon name="dropdown" />}
+      selectorIcon={<Icon name="Dropdown" />}
       isClearable={false} // so the "cross" button does not show
       classNames={{
         selectorButton: clsx(
diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx
index f46879e..a6f71b3 100644
--- a/src/components/Icon.tsx
+++ b/src/components/Icon.tsx
@@ -1,54 +1,11 @@
 import { ReactSVG } from "react-svg";
 import { clsx } from "clsx";
-import Icons from "src/assets/icons";
+import * as Icons from "src/assets/icons";
 import { TextColorType } from "./utils/colors.ts";
 import { textColor } from "./utils/classes.ts";
 
-export const iconNameOptions = [
-  "attention-circle",
-  "checkmark",
-  "checkmark-circle",
-  "cross",
-  "cross-circle",
-  "datepicker",
-  "download",
-  "dropdown",
-  "edit",
-  "expand",
-  "eye",
-  "info-circle",
-  "layer",
-  "magnifier",
-  "minus",
-  "next",
-  "plus",
-  "previous",
-  "up",
-] as const; // for storybook dropdown
-
-export type IconName = (typeof iconNameOptions)[number]; // for storybook dropdown
-
-const iconMap = {
-  "attention-circle": Icons.AttentionCircle,
-  checkmark: Icons.Checkmark,
-  "checkmark-circle": Icons.CheckmarkCircle,
-  cross: Icons.Cross,
-  "cross-circle": Icons.CrossCircle,
-  datepicker: Icons.Datepicker,
-  download: Icons.Download,
-  dropdown: Icons.Dropdown,
-  expand: Icons.Expand,
-  edit: Icons.Edit,
-  eye: Icons.Eye,
-  "info-circle": Icons.InfoCircle,
-  layer: Icons.Layer,
-  magnifier: Icons.Magnifier,
-  minus: Icons.Minus,
-  next: Icons.Next,
-  plus: Icons.Plus,
-  previous: Icons.Previous,
-  up: Icons.Up,
-};
+export type IconName = keyof typeof Icons;
+export const iconNameOptions = Object.keys(Icons) as IconName[]; // for storybook dropdown
 
 type IconProps = {
   name: IconName;
@@ -64,7 +21,7 @@ type IconProps = {
  * @param customClass: string | undefined
  */
 const Icon = ({ name, color = null, customClass = "" }: IconProps) => {
-  const src = iconMap[name];
+  const src = Icons[name];
 
   if (!color && !customClass) {
     return <ReactSVG src={src} style={{ color: "currentColor" }} />;
diff --git a/src/components/Menu.tsx b/src/components/Menu.tsx
index 30aae91..efa513e 100644
--- a/src/components/Menu.tsx
+++ b/src/components/Menu.tsx
@@ -90,7 +90,7 @@ export const ActionMenu = ({ label, items, onAction }: MenuProps) => {
       onAction={onAction}
       menuTrigger={
         <NextButton
-          startContent={<Icon name="dropdown" />}
+          startContent={<Icon name="Dropdown" />}
           className={buttonBaseClass} // same as Button component (src/components/Button.tsx)
           type="button"
           color="primary"
@@ -133,7 +133,7 @@ export const ProfileMenu = ({ label, items, onAction }: MenuProps) => {
               />
             }
           />
-          <Icon name="dropdown" color="primary" />
+          <Icon name="Dropdown" color="primary" />
         </div>
       }
     />
diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx
index d28cc3a..ad64622 100644
--- a/src/components/Modal.tsx
+++ b/src/components/Modal.tsx
@@ -36,7 +36,7 @@ const Modal = ({
 }: ModalProps) => {
   const closeButton = (
     <button>
-      <Icon name="cross" />
+      <Icon name="Cross" />
     </button>
   );
   return (
diff --git a/src/stories/Button.stories.tsx b/src/stories/Button.stories.tsx
index dff794e..276f500 100644
--- a/src/stories/Button.stories.tsx
+++ b/src/stories/Button.stories.tsx
@@ -29,7 +29,7 @@ export const IconButton: StoryObj = {
   args: {
     label: "Download",
     color: "primary",
-    icon: "download",
+    icon: "Download",
   },
   argTypes: {
     icon: { control: "select", options: iconNameOptions },
diff --git a/src/stories/Icon.stories.ts b/src/stories/Icon.stories.ts
index 4d6294a..9065a47 100644
--- a/src/stories/Icon.stories.ts
+++ b/src/stories/Icon.stories.ts
@@ -1,5 +1,5 @@
 import { Meta, StoryObj } from "@storybook/react";
-import Icon from "src/components/Icon.tsx";
+import Icon, { iconNameOptions } from "src/components/Icon.tsx";
 import { textColorOptions } from "src/components/utils/colors.ts";
 
 /** Icons are visual symbols used to represent ideas, objects, or actions. Icons can also be used in buttons. They communicate messages at a glance, afford interactivity, and draw attention to important information. */
@@ -7,6 +7,7 @@ const meta = {
   title: "Components/Icon",
   component: Icon,
   argTypes: {
+    name: { control: "select", options: iconNameOptions },
     color: { control: "select", options: textColorOptions },
   },
 } satisfies Meta;
@@ -15,7 +16,7 @@ export default meta;
 
 export const SimpleIcon: StoryObj = {
   args: {
-    name: "attention-circle",
+    name: "AttentionCircle",
     color: "body",
     customClass: "w-[16px] h-[16px]",
   },
diff --git a/src/stories/Menu.stories.tsx b/src/stories/Menu.stories.tsx
index 2a44ff1..624737f 100644
--- a/src/stories/Menu.stories.tsx
+++ b/src/stories/Menu.stories.tsx
@@ -14,9 +14,9 @@ const meta = {
 export default meta;
 
 const items = [
-  { key: "expand", itemName: "Expand", iconName: "expand" },
-  { key: "add", itemName: "Add", iconName: "plus" },
-  { key: "remove", itemName: "Remove", iconName: "cross", danger: true },
+  { key: "expand", itemName: "Expand", iconName: "Expand" },
+  { key: "add", itemName: "Add", iconName: "Plus" },
+  { key: "remove", itemName: "Remove", iconName: "Cross", danger: true },
 ];
 
 export const ActionMenuWithoutIcons = () => {
-- 
GitLab