Repository: microsoft/griffel
Pull request #113 (2022-05-03 14:11:32)

feat(devtools): add filter
Add filter for devtools:
<img width="418" alt="Screenshot 2022-05-03 at 16 06 15" src="https://user-images.githubusercontent.com/28751745/166469530-1ffc24c7-4ae1-4594-9861-8da7fe5bfd80.png">


github-actions[bot]: ## 📊 Bundle size report

<sub>🤖 This report was generated against <a href='https://github.com/microsoft/griffel/commit/03f45c8262b34a0b96a8281e30c47a4046b05899'>03f45c8262b34a0b96a8281e30c47a4046b05899</a></sub>

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/packages/devtools/src/FlattenView.tsx b/packages/devtools/src/FlattenView.tsx
index 3ed55ac..c60cfbd 100644
--- a/packages/devtools/src/FlattenView.tsx
+++ b/packages/devtools/src/FlattenView.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
 import { makeStaticStyles, makeStyles, shorthands } from '@griffel/react';
 
 import { SlotCSSRules } from './SlotCSSRules';
-import { getRulesBySlots } from './utils';
+import { filterSlots, getRulesBySlots } from './utils';
 import { tokens } from './themes';
 import { ViewContext } from './ViewContext';
 
@@ -12,10 +12,38 @@ const useStyles = makeStyles({
   root: {
     backgroundColor: tokens.background,
     color: tokens.foreground,
-    paddingBottom: '10px',
+  },
+  bar: {
+    alignItems: 'center',
+    display: 'flex',
+    columnGap: '4px',
+    fontFamily: 'system-ui',
+    fontSize: '12px',
+  },
+  input: {
+    color: 'inherit',
+    flexGrow: 1,
+    backgroundColor: tokens.slotNameBackground,
+    ...shorthands.margin('2px'),
+    ...shorthands.padding('2px'),
+    ...shorthands.border('1px', 'solid', tokens.slotNameBorder),
+
+    ':focus-visible': {
+      backgroundColor: 'inherit',
+      outlineStyle: 'solid',
+      outlineWidth: 'thin',
+    },
   },
   info: {
-    ...shorthands.margin(0, '5px'),
+    cursor: 'help',
+    textDecorationLine: 'underline',
+    textDecorationStyle: 'dotted',
+    marginRight: '4px',
+  },
+  rules: {
+    display: 'flex',
+    flexDirection: 'column',
+    rowGap: '5px',
   },
 });
 
@@ -41,14 +69,34 @@ export const FlattenView: React.FC<FlattenViewProps> = props => {
   const [highlightedClass, setHighlightedClass] = React.useState('');
   const contextValue = React.useMemo(() => ({ highlightedClass, setHighlightedClass }), [highlightedClass]);
 
+  const [searchTerm, setSearchTerm] = React.useState('');
+  const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    setSearchTerm(event.target.value);
+  };
+
+  const filteredSlots = React.useMemo(() => filterSlots(slots, searchTerm), [slots, searchTerm]);
+
   return (
     <div className={classes.root}>
-      <div className={classes.info}>direction: {debugResultRoot.direction}</div>
-      <ViewContext.Provider value={contextValue}>
-        {slots.map(({ slot, rules }) => (
-          <SlotCSSRules key={slot} slot={slot} atomicRules={rules} />
-        ))}
-      </ViewContext.Provider>
+      <div className={classes.bar}>
+        <input
+          type="text"
+          placeholder="Filter"
+          className={classes.input}
+          value={searchTerm}
+          onChange={handleSearchChange}
+        />
+        <div className={classes.info} title={`"dir"=${debugResultRoot.direction}`}>
+          {debugResultRoot.direction.toLocaleUpperCase()}
+        </div>
+      </div>
+      <div className={classes.rules}>
+        <ViewContext.Provider value={contextValue}>
+          {filteredSlots.map(({ slot, rules }) => (
+            <SlotCSSRules key={slot} slot={slot} atomicRules={rules} />
+          ))}
+        </ViewContext.Provider>
+      </div>
     </div>
   );
 };
diff --git a/packages/devtools/src/SlotCSSRules.tsx b/packages/devtools/src/SlotCSSRules.tsx
index d54834e..1916076 100644
--- a/packages/devtools/src/SlotCSSRules.tsx
+++ b/packages/devtools/src/SlotCSSRules.tsx
@@ -12,7 +12,6 @@ const useStyles = makeStyles({
   slotName: {
     cursor: 'pointer',
     ...shorthands.padding('2px', '5px'),
-    ...shorthands.margin('5px', 0),
     ...shorthands.borderTop('1px', 'solid', tokens.slotNameBorder),
     ...shorthands.borderBottom('1px', 'solid', tokens.slotNameBorder),
     backgroundColor: tokens.slotNameBackground,
@@ -33,7 +32,7 @@ export const SlotCSSRules: React.FC<{ slot: string; atomicRules: AtomicRules[] }
   const undoHighlight = () => setHighlightedClass('');
 
   return (
-    <>
+    <div>
       <pre className={classes.slotName} onClick={toggleExpanded}>
         {slot}
       </pre>
@@ -42,6 +41,6 @@ export const SlotCSSRules: React.FC<{ slot: string; atomicRules: AtomicRules[] }
           <MonolithicRulesView rules={rules} />
         </div>
       )}
-    </>
+    </div>
   );
 };
diff --git a/packages/devtools/src/utils.ts b/packages/devtools/src/utils.ts
index 9b68c35..59027af 100644
--- a/packages/devtools/src/utils.ts
+++ b/packages/devtools/src/utils.ts
@@ -23,3 +23,19 @@ export function getRulesBySlots(node: DebugResult, result: SlotInfo[] = []): Slo
     return [...acc, ...getRulesBySlots(child)];
   }, result);
 }
+
+export function filterSlots(slots: SlotInfo[], searchTerm: string) {
+  if (!searchTerm.length) {
+    return slots;
+  }
+
+  return slots.reduce<SlotInfo[]>((acc, { slot, rules }) => {
+    const filteredRules = rules.filter(rule => rule.cssRule.includes(searchTerm));
+
+    if (filteredRules.length) {
+      return [...acc, { slot, rules: filteredRules }];
+    }
+
+    return acc;
+  }, []);
+}


Test patch:
diff --git a/packages/devtools/src/utils.test.ts b/packages/devtools/src/utils.test.ts
index b01a293..b50381a 100644
--- a/packages/devtools/src/utils.test.ts
+++ b/packages/devtools/src/utils.test.ts
@@ -1,5 +1,6 @@
-import { getRulesBySlots } from './utils';
+import { filterSlots, getRulesBySlots } from './utils';
 import type { DebugResult } from '@griffel/core';
+import type { SlotInfo } from './types';
 
 describe('getRulesBySlots', () => {
   it('traverse debug result, gather and return all makeStyles slot name and rules', () => {
@@ -72,3 +73,48 @@ describe('getRulesBySlots', () => {
     ]);
   });
 });
+
+describe('filterSlots', () => {
+  it('returns filter results without modifying the searched object', () => {
+    const slots: SlotInfo[] = [
+      {
+        slot: 'slot2',
+        rules: [
+          {
+            cssRule: '.fabcde2{display:none;}',
+          },
+        ],
+      },
+      {
+        slot: 'slot1',
+        rules: [
+          {
+            cssRule: '.fabcde1{display:block;}',
+            overriddenBy: 'fabcde2',
+          },
+          {
+            cssRule: '.fabcd11{color:blue;}',
+            overriddenBy: 'fabcd11',
+          },
+        ],
+      },
+      {
+        slot: 'root',
+        rules: [
+          {
+            cssRule: '.fabcde0{background-color:transparent;}',
+          },
+        ],
+      },
+    ];
+    const slotsString = JSON.stringify(slots);
+    expect(filterSlots(slots, 'display')).toEqual([
+      slots[0],
+      {
+        slot: 'slot1',
+        rules: [slots[1].rules[0]],
+      },
+    ]);
+    expect(JSON.stringify(slots)).toEqual(slotsString); // slots itself is not modified
+  });
+});