From db4e130036b0cf0623c7ee451cc08da7a5396207 Mon Sep 17 00:00:00 2001
From: Leonardo Christino <leomilho@gmail.com>
Date: Tue, 16 Apr 2024 17:01:08 +0200
Subject: [PATCH] feat(style): refactor and update pill style

---
 libs/shared/lib/components/index.ts           |   1 +
 libs/shared/lib/components/pills/Pill.tsx     | 158 ++++++++++++++++
 .../lib/components/pills/PillHandle.tsx       |  81 ++++++++
 libs/shared/lib/components/pills/index.ts     |   1 +
 .../shared/lib/components/pills/pill.const.ts |   7 +
 .../components/pills/stories/pill.stories.tsx |  25 +++
 .../pills/stories/pillEntity.stories.tsx      |  35 ++++
 .../pills/stories/pillLogic.stories.tsx       |  27 +++
 .../pills/stories/pillRelation.stories.tsx    |  35 ++++
 libs/shared/lib/querybuilder/model/index.ts   |   5 +-
 .../lib/querybuilder/panel/querybuilder.tsx   |  10 +-
 .../entitypill/QueryEntityPill.tsx            |  88 +++++++++
 .../entitypill/entitypill-full.stories.tsx    |  37 ----
 .../entitypill/entitypill.stories.tsx         |  42 ----
 .../customFlowPills/entitypill/entitypill.tsx |  89 ---------
 .../pills/customFlowPills/entitypill/index.ts |   2 +-
 .../{logicInput.tsx => LogicInput.tsx}        |   3 +-
 .../{logicpill.tsx => QueryLogicPill.tsx}     |  35 +---
 .../relationpill/QueryRelationPill.tsx        | 179 ++++++++++++++++++
 .../customFlowPills/relationpill/index.ts     |   2 +-
 .../relation-full_reactflow.stories.tsx       |  41 ----
 .../relationpill/relation-handles.tsx         |  59 ------
 .../relationpill/relationpill.module.scss     | 119 ------------
 .../relationpill.module.scss.d.ts             |  34 ----
 .../relationpill/relationpill.stories.tsx     |  55 ------
 .../relationpill/relationpill.tsx             | 173 -----------------
 .../{pilldropdown.tsx => PillDropdown.tsx}    |  45 +++--
 .../pilldropdown/pilldropdown.module.scss     |   1 -
 .../pilldropdown.module.scss.d.ts             |  23 ---
 libs/shared/lib/schema/panel/schema.tsx       |   8 +-
 .../{entity-node.tsx => SchemaEntityPill.tsx} |  68 +++----
 .../nodes/entity/entity-node.stories.tsx      | 106 -----------
 .../pills/nodes/entity/entity.module.scss     |  35 ----
 .../nodes/entity/entity.module.scss.d.ts      |   7 -
 ...lation-node.tsx => SchemaRelationPill.tsx} |  49 ++---
 .../nodes/relation/relation-node.stories.tsx  | 109 -----------
 .../pills/nodes/relation/relation.module.scss |  50 -----
 .../nodes/relation/relation.module.scss.d.ts  |   9 -
 38 files changed, 733 insertions(+), 1120 deletions(-)
 create mode 100644 libs/shared/lib/components/pills/Pill.tsx
 create mode 100644 libs/shared/lib/components/pills/PillHandle.tsx
 create mode 100644 libs/shared/lib/components/pills/index.ts
 create mode 100644 libs/shared/lib/components/pills/pill.const.ts
 create mode 100644 libs/shared/lib/components/pills/stories/pill.stories.tsx
 create mode 100644 libs/shared/lib/components/pills/stories/pillEntity.stories.tsx
 create mode 100644 libs/shared/lib/components/pills/stories/pillLogic.stories.tsx
 create mode 100644 libs/shared/lib/components/pills/stories/pillRelation.stories.tsx
 create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx
 delete mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx
 delete mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.stories.tsx
 delete mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
 rename libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/{logicInput.tsx => LogicInput.tsx} (86%)
 rename libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/{logicpill.tsx => QueryLogicPill.tsx} (78%)
 create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx
 delete mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx
 delete mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-handles.tsx
 delete mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss
 delete mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts
 delete mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx
 delete mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx
 rename libs/shared/lib/querybuilder/pills/pilldropdown/{pilldropdown.tsx => PillDropdown.tsx} (65%)
 delete mode 100644 libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.module.scss
 delete mode 100644 libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.module.scss.d.ts
 rename libs/shared/lib/schema/pills/nodes/entity/{entity-node.tsx => SchemaEntityPill.tsx} (50%)
 delete mode 100644 libs/shared/lib/schema/pills/nodes/entity/entity-node.stories.tsx
 delete mode 100644 libs/shared/lib/schema/pills/nodes/entity/entity.module.scss
 delete mode 100644 libs/shared/lib/schema/pills/nodes/entity/entity.module.scss.d.ts
 rename libs/shared/lib/schema/pills/nodes/relation/{relation-node.tsx => SchemaRelationPill.tsx} (59%)
 delete mode 100644 libs/shared/lib/schema/pills/nodes/relation/relation-node.stories.tsx
 delete mode 100644 libs/shared/lib/schema/pills/nodes/relation/relation.module.scss
 delete mode 100644 libs/shared/lib/schema/pills/nodes/relation/relation.module.scss.d.ts

diff --git a/libs/shared/lib/components/index.ts b/libs/shared/lib/components/index.ts
index 7a58d1a1a..46862ade6 100644
--- a/libs/shared/lib/components/index.ts
+++ b/libs/shared/lib/components/index.ts
@@ -15,3 +15,4 @@ export * from './Legend';
 export * from './LoadingSpinner';
 export * from './Popup';
 export * from './Resizable';
+export * from './pills';
diff --git a/libs/shared/lib/components/pills/Pill.tsx b/libs/shared/lib/components/pills/Pill.tsx
new file mode 100644
index 000000000..6045c582e
--- /dev/null
+++ b/libs/shared/lib/components/pills/Pill.tsx
@@ -0,0 +1,158 @@
+import React, { useState } from 'react';
+import { pillWidth, pillHeight, pillXPadding, pillInnerMargin, topLineHeight, pillBorderWidth, pillDropdownPadding } from './pill.const';
+import { Position } from 'reactflow';
+import { PillHandle } from './PillHandle';
+
+export type PillI = {
+  onHovered?: (hovered: boolean) => void;
+  // onDragged: (dragged: boolean) => void;
+  title: string | React.ReactNode;
+  children?: React.ReactNode;
+  handles?: React.ReactNode;
+  style?: React.CSSProperties;
+  corner?: 'rounded' | 'square' | 'diamond';
+  topColor: string;
+  handleUp?: React.ReactNode;
+  handleDown?: React.ReactNode;
+  handleLeft?: React.ReactNode;
+  handleRight?: React.ReactNode;
+};
+
+export const Pill = React.memo((props: PillI) => {
+  const [hovered, setHovered] = useState(false);
+
+  const onMouseEnter = (event: React.MouseEvent) => {
+    if (!hovered) {
+      setHovered(true);
+      if (props.onHovered) props.onHovered(true);
+    }
+  };
+
+  const onMouseLeave = (event: React.MouseEvent) => {
+    if (hovered) {
+      setHovered(false);
+      if (props.onHovered) props.onHovered(false);
+    }
+  };
+
+  const width = pillWidth;
+  const corner = props.corner || 'rounded';
+
+  const innerContentStyle = {
+    minWidth: corner === 'diamond' ? width - pillBorderWidth * 2 : width - pillBorderWidth * 2,
+    maxWidth: corner === 'diamond' ? width - pillBorderWidth * 2 : width - pillBorderWidth * 2,
+    minHeight: pillHeight - pillBorderWidth * 2,
+    maxHeight: pillHeight - pillBorderWidth * 2,
+    margin: pillBorderWidth,
+    clipPath: corner === 'diamond' ? 'polygon(0.5% 50%, 4.9% 0%, 95.1% 0%, 99.5% 50%, 95.1% 100%, 4.9% 100%)' : '',
+  };
+
+  const outerContentStyle = {
+    minWidth: width,
+    maxWidth: width,
+    minHeight: pillHeight,
+    maxHeight: pillHeight,
+    clipPath: corner === 'diamond' ? 'polygon(0% 50%, 5% 0%, 95% 0%, 100% 50%, 95% 100%, 5% 100%)' : '',
+  };
+
+  return (
+    <div className="flex flex-row flex-grow-0 text-xs">
+      <div
+        className={'bg-secondary-200 ' + (corner !== 'square' ? 'rounded' : '')}
+        style={{
+          ...outerContentStyle,
+        }}
+      >
+        <div
+          className="absolute -z-10"
+          style={{
+            top: pillHeight - pillInnerMargin,
+            width: pillWidth,
+            paddingLeft: pillDropdownPadding,
+            paddingRight: pillDropdownPadding,
+          }}
+        >
+          {props.children}
+        </div>
+        <div
+          className={'bg-secondary-100 ' + (corner !== 'square' ? 'rounded-[3px]' : '')}
+          style={{
+            ...innerContentStyle,
+          }}
+        >
+          {props.topColor && (
+            <div className={(corner !== 'square' ? 'rounded-t ' : '') + props.topColor} style={{ height: topLineHeight }}></div>
+          )}
+          <div
+            className={'font-semibold bg-neutral-100 ' + (corner !== 'square' ? 'rounded-b-[3px]' : '')}
+            onMouseEnter={onMouseEnter}
+            onMouseLeave={onMouseLeave}
+          >
+            <div
+              className=""
+              style={{
+                transform: `translateY(${topLineHeight / 3}px)`,
+                paddingLeft: pillXPadding + (corner === 'diamond' ? 5 : 0),
+                paddingRight: pillXPadding + (corner === 'diamond' ? 5 : 0),
+              }}
+            >
+              {props.title}
+            </div>
+          </div>
+        </div>
+      </div>
+      <div className="absolute z-50 pointer-events-auto">{props.handles}</div>
+    </div>
+  );
+});
+
+export const EntityPill = React.memo((props: Omit<PillI, 'topColor'> & { withHandles?: 'vertical' | 'horizontal' }) => {
+  const handles = !props.withHandles ? undefined : props.withHandles === 'horizontal' ? (
+    <>
+      <PillHandle position={Position.Left} className={'fill-accent-500 stroke-white'} type="square">
+        {props.handleLeft}
+      </PillHandle>
+      <PillHandle position={Position.Right} className={'fill-accent-500 stroke-white'} type="square">
+        {props.handleRight}
+      </PillHandle>
+    </>
+  ) : (
+    <>
+      <PillHandle position={Position.Top} className={'fill-accent-500 stroke-white stroke-1'} type="arrowUp">
+        {props.handleUp}
+      </PillHandle>
+      <PillHandle position={Position.Bottom} className={'fill-accent-500 stroke-white stroke-1'} type="arrowDown">
+        {props.handleDown}
+      </PillHandle>
+    </>
+  );
+  return <Pill {...props} corner="rounded" topColor="bg-accent-500" handles={handles} />;
+});
+
+export const RelationPill = React.memo((props: Omit<PillI, 'topColor'> & { withHandles?: 'vertical' | 'horizontal' }) => {
+  const handles = !props.withHandles ? undefined : props.withHandles === 'horizontal' ? (
+    <>
+      <PillHandle position={Position.Left} className={'fill-[#0676C1] stroke-white'} type="square">
+        {props.handleLeft}
+      </PillHandle>
+      <PillHandle position={Position.Right} className={'fill-[#0676C1] stroke-white'} type="square" mr={-pillBorderWidth}>
+        {props.handleRight}
+      </PillHandle>
+    </>
+  ) : (
+    <>
+      <PillHandle position={Position.Top} className={'fill-[#0676C1] stroke-white stroke-1'} type="arrowUp">
+        {props.handleUp}
+      </PillHandle>
+      <PillHandle position={Position.Bottom} className={'fill-[#0676C1] stroke-white stroke-1'} type="arrowDown">
+        {props.handleDown}
+      </PillHandle>
+    </>
+  );
+
+  return <Pill {...props} corner="diamond" topColor="bg-[#0676C1]" handles={handles} />;
+});
+
+export const LogicPill = React.memo((props: Omit<PillI, 'topColor'>) => {
+  return <Pill {...props} corner="square" topColor="bg-[#543719]" />;
+});
diff --git a/libs/shared/lib/components/pills/PillHandle.tsx b/libs/shared/lib/components/pills/PillHandle.tsx
new file mode 100644
index 000000000..e2e04d0f9
--- /dev/null
+++ b/libs/shared/lib/components/pills/PillHandle.tsx
@@ -0,0 +1,81 @@
+import React, { useMemo } from 'react';
+import { Position } from 'reactflow';
+import { pillHeight, pillWidth, topLineHeight, pillBorderWidth } from './pill.const';
+
+export const PillHandle = (props: {
+  handleTop?: 'auto' | 'fixed';
+  hidden?: boolean;
+  isValidConnection?: boolean;
+  children?: React.ReactNode;
+  className?: string;
+  position: Position;
+  type: 'square' | 'arrowUp' | 'arrowDown';
+  mr?: number;
+  outerSize?: number;
+  innerSize?: number;
+}) => {
+  const outerSize = props.outerSize || (props.type === 'square' ? 6 : 4);
+  const innerSize = props.innerSize || (props.type === 'square' ? 4 : 6);
+
+  const style: React.CSSProperties = {
+    width: outerSize * 2,
+    height: outerSize * 2,
+  };
+  if (props.position === Position.Left) {
+    style.left = -outerSize; // round size
+    style.top = props.handleTop === 'auto' ? `auto` : pillHeight / 2 - outerSize;
+  } else if (props.position === Position.Right) {
+    style.left = pillWidth + (props.mr || 0) - outerSize; // width of pill
+    style.top = props.handleTop === 'auto' ? `auto` : pillHeight / 2 - outerSize;
+  } else if (props.position === Position.Top) {
+    style.left = pillWidth / 2 - outerSize;
+    style.top = -outerSize - innerSize / 2 + topLineHeight + pillBorderWidth;
+  } else if (props.position === Position.Bottom) {
+    style.left = pillWidth / 2 - outerSize;
+    style.top = pillHeight - outerSize + innerSize / 2 - topLineHeight;
+  }
+
+  const innerStyle: React.CSSProperties = { width: innerSize * 2, height: innerSize * 2 };
+  innerStyle.left = outerSize - innerSize;
+  innerStyle.top = outerSize - innerSize;
+
+  const innerHandle = useMemo(() => {
+    if (props.type === 'square')
+      return (
+        <svg className={'absolute pointer-events-none'} style={innerStyle} width="8" height="8" viewBox="0 0 8 8" fill="none">
+          <rect x="0.5" y="0.5" width="7" height="7" className={props.className} />
+        </svg>
+      );
+    if (props.type === 'arrowUp')
+      return (
+        <svg width="14" height="7" viewBox="0 0 14 7" fill="none" className={'absolute pointer-events-none'} style={innerStyle}>
+          <path d="M14 7H0L7 0L14 7Z" className={props.className} />
+        </svg>
+      );
+    if (props.type === 'arrowDown')
+      return (
+        <svg width="14" height="7" viewBox="0 0 14 7" fill="none" className={'absolute pointer-events-none'} style={innerStyle}>
+          <path d="M14 0H0L7 7L14 0Z" className={props.className} />
+        </svg>
+      );
+    return <></>;
+  }, [props.type]);
+
+  return (
+    <>
+      <div
+        className="absolute z-40"
+        style={{
+          ...style,
+          top: style.top as number,
+          left: style.left as number,
+        }}
+      >
+        {props.children}
+      </div>
+      <div className="absolute z-40 pointer-events-none" style={style}>
+        {innerHandle}
+      </div>
+    </>
+  );
+};
diff --git a/libs/shared/lib/components/pills/index.ts b/libs/shared/lib/components/pills/index.ts
new file mode 100644
index 000000000..c32b08563
--- /dev/null
+++ b/libs/shared/lib/components/pills/index.ts
@@ -0,0 +1 @@
+export * from './Pill';
diff --git a/libs/shared/lib/components/pills/pill.const.ts b/libs/shared/lib/components/pills/pill.const.ts
new file mode 100644
index 000000000..b888e15cd
--- /dev/null
+++ b/libs/shared/lib/components/pills/pill.const.ts
@@ -0,0 +1,7 @@
+export const pillHeight = 26;
+export const pillInnerMargin = 2;
+export const pillXPadding = 10;
+export const pillDropdownPadding = 2;
+export const pillWidth = 154;
+export const topLineHeight = 3;
+export const pillBorderWidth = 1;
diff --git a/libs/shared/lib/components/pills/stories/pill.stories.tsx b/libs/shared/lib/components/pills/stories/pill.stories.tsx
new file mode 100644
index 000000000..0e4e18dcf
--- /dev/null
+++ b/libs/shared/lib/components/pills/stories/pill.stories.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { Meta, StoryObj } from '@storybook/react';
+import { configureStore } from '@reduxjs/toolkit';
+import { Provider } from 'react-redux';
+
+import { Pill } from '../Pill';
+
+const Component: Meta<typeof Pill> = {
+  title: 'Pills/Pill',
+  component: Pill,
+  decorators: [(story) => <Provider store={Mockstore}>{story()}</Provider>],
+};
+
+export default Component;
+
+const Mockstore = configureStore({
+  reducer: {},
+});
+
+export const Default = {
+  args: {
+    title: 'TestEntity',
+    children: <div></div>,
+  },
+};
diff --git a/libs/shared/lib/components/pills/stories/pillEntity.stories.tsx b/libs/shared/lib/components/pills/stories/pillEntity.stories.tsx
new file mode 100644
index 000000000..3d6984cb5
--- /dev/null
+++ b/libs/shared/lib/components/pills/stories/pillEntity.stories.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { Meta, StoryObj } from '@storybook/react';
+import { configureStore } from '@reduxjs/toolkit';
+import { Provider } from 'react-redux';
+import { Position } from 'reactflow';
+
+import { EntityPill } from '../Pill';
+import { PillHandle } from '../PillHandle';
+
+const Component: Meta<typeof EntityPill> = {
+  title: 'Pills/Pill',
+  component: EntityPill,
+  decorators: [
+    (story) => (
+      <Provider store={Mockstore}>
+        <div className="m-10">{story()}</div>
+      </Provider>
+    ),
+  ],
+};
+
+export default Component;
+
+const Mockstore = configureStore({
+  reducer: {},
+});
+
+export const EntityPillStory = {
+  args: {
+    onHovered: (hovered: boolean) => {},
+    onDragged: (dragged: boolean) => {},
+    title: 'TestEntity',
+    children: <div></div>,
+  },
+};
diff --git a/libs/shared/lib/components/pills/stories/pillLogic.stories.tsx b/libs/shared/lib/components/pills/stories/pillLogic.stories.tsx
new file mode 100644
index 000000000..4c2dc4321
--- /dev/null
+++ b/libs/shared/lib/components/pills/stories/pillLogic.stories.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { Meta, StoryObj } from '@storybook/react';
+import { configureStore } from '@reduxjs/toolkit';
+import { Provider } from 'react-redux';
+
+import { LogicPill } from '../Pill';
+
+const Component: Meta<typeof LogicPill> = {
+  title: 'Pills/Pill',
+  component: LogicPill,
+  decorators: [(story) => <Provider store={Mockstore}>{story()}</Provider>],
+};
+
+export default Component;
+
+const Mockstore = configureStore({
+  reducer: {},
+});
+
+export const LogicPillStory = {
+  args: {
+    onHovered: (hovered: boolean) => {},
+    onDragged: (dragged: boolean) => {},
+    title: 'TestEntity',
+    children: <div></div>,
+  },
+};
diff --git a/libs/shared/lib/components/pills/stories/pillRelation.stories.tsx b/libs/shared/lib/components/pills/stories/pillRelation.stories.tsx
new file mode 100644
index 000000000..08f8a035c
--- /dev/null
+++ b/libs/shared/lib/components/pills/stories/pillRelation.stories.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { Meta, StoryObj } from '@storybook/react';
+import { configureStore } from '@reduxjs/toolkit';
+import { Provider } from 'react-redux';
+
+import { RelationPill } from '../Pill';
+import { PillHandle } from '../PillHandle';
+import { Position } from 'reactflow';
+
+const Component: Meta<typeof RelationPill> = {
+  title: 'Pills/Pill',
+  component: RelationPill,
+  decorators: [
+    (story) => (
+      <Provider store={Mockstore}>
+        <div className="m-10">{story()}</div>
+      </Provider>
+    ),
+  ],
+};
+
+export default Component;
+
+const Mockstore = configureStore({
+  reducer: {},
+});
+
+export const RelationPillStory = {
+  args: {
+    onHovered: (hovered: boolean) => {},
+    onDragged: (dragged: boolean) => {},
+    title: 'TestEntity',
+    children: <div></div>,
+  },
+};
diff --git a/libs/shared/lib/querybuilder/model/index.ts b/libs/shared/lib/querybuilder/model/index.ts
index 4fadc5634..f5ea6888a 100644
--- a/libs/shared/lib/querybuilder/model/index.ts
+++ b/libs/shared/lib/querybuilder/model/index.ts
@@ -8,7 +8,8 @@ export * from './logic';
 export * from './reactflow';
 
 type ExtraProps = { extra?: string; separator?: string };
-export function toHandleId(handleData: QueryGraphEdgeHandle, separator: string = '__'): string {
+export function toHandleId(handleData?: QueryGraphEdgeHandle, separator: string = '__'): string {
+  if (!handleData) throw Error('handleData is not defined');
   // if (!extra) extra = '';
   if (!separator) separator = '__';
   return [
@@ -26,7 +27,7 @@ export function toHandleId(handleData: QueryGraphEdgeHandle, separator: string =
 export function handleDataFromReactflowToDataId(
   node: SchemaReactflowNode,
   attribute: NodeAttribute,
-  options: ExtraProps = {}
+  options: ExtraProps = {},
 ): QueryGraphEdgeHandle {
   if (!node.data.name) throw Error('node.data is not defined');
   return {
diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
index 1491281f2..e8224d3cc 100644
--- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx
+++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
@@ -31,8 +31,8 @@ import { addError } from '../../data-access/store/configSlice';
 import { toSchemaGraphology } from '../../data-access/store/schemaSlice';
 import { LayoutFactory } from '../../graph-layout';
 import { AllLogicMap, QueryElementTypes, createReactFlowElements, isLogicHandle, toHandleData } from '../model';
-import { ConnectionDragLine, ConnectionLine, EntityFlowElement, RelationPill } from '../pills';
-import { LogicPill } from '../pills/customFlowPills/logicpill/logicpill';
+import { ConnectionDragLine, ConnectionLine, QueryEntityPill, QueryRelationPill } from '../pills';
+import { QueryLogicPill } from '../pills/customFlowPills/logicpill/QueryLogicPill';
 import { dragPillStarted, movePillTo } from '../pills/dragging/dragPill';
 import styles from './querybuilder.module.scss';
 import { QueryBuilderLogicPillsPanel } from './querysidepanel/queryBuilderLogicPillsPanel';
@@ -59,9 +59,9 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
 
   var nodeTypes = useMemo(
     () => ({
-      entity: EntityFlowElement,
-      relation: RelationPill,
-      logic: LogicPill,
+      entity: QueryEntityPill,
+      relation: QueryRelationPill,
+      logic: QueryLogicPill,
     }),
     [],
   );
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx
new file mode 100644
index 000000000..43ac8efd1
--- /dev/null
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/QueryEntityPill.tsx
@@ -0,0 +1,88 @@
+import { useQuerybuilderGraph } from '@graphpolaris/shared/lib/data-access';
+import React, { useMemo, useState } from 'react';
+import { Handle, Position } from 'reactflow';
+import { NodeAttribute, SchemaReactflowEntityNode, toHandleId } from '../../../model';
+import { PillDropdown } from '../../pilldropdown/PillDropdown';
+import { EntityPill } from '@graphpolaris/shared/lib/components';
+
+/**
+ * Component to render an entity flow element
+ * @param {NodeProps} param0 The data of an entity flow element.
+ */
+export const QueryEntityPill = React.memo((node: SchemaReactflowEntityNode) => {
+  const data = node.data;
+  if (!data.leftRelationHandleId) throw new Error('EntityFlowElement: data.leftRelationHandleId is undefined');
+  if (!data.rightRelationHandleId) throw new Error('EntityFlowElement: data.rightRelationHandleId is undefined');
+
+  const graph = useQuerybuilderGraph();
+  const attributeEdges = useMemo(
+    () => graph.edges.filter((edge) => edge.source === node.id && !!edge?.attributes?.sourceHandleData.attributeType),
+    [graph],
+  );
+
+  const [hovered, setHovered] = useState(false);
+  const [handleBeingDragged, setHandleBeingDragged] = useState(-1);
+
+  const onMouseEnter = (event: React.MouseEvent) => {
+    if (!hovered) setHovered(true);
+  };
+
+  const onMouseLeave = (event: React.MouseEvent) => {
+    if (hovered) setHovered(false);
+  };
+
+  const onHandleMouseDown = (attribute: NodeAttribute, i: number, event: React.MouseEvent) => {
+    setHandleBeingDragged(i);
+    window.addEventListener('mouseup', onHandleMouseUp, true);
+  };
+
+  const onHandleMouseUp = () => {
+    setHandleBeingDragged(-1);
+    window.removeEventListener('mouseup', onHandleMouseUp, true);
+  };
+
+  const onConnect = (params: any) => {
+    console.log('EntityPill onConnect', params);
+  };
+
+  return (
+    <div className="w-fit h-fit" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
+      <EntityPill
+        title={data.name || ''}
+        withHandles="horizontal"
+        handleLeft={
+          <Handle
+            id={toHandleId(data.leftRelationHandleId)}
+            position={Position.Left}
+            className={'!rounded-none !bg-transparent !w-full !h-full !border-0 !right-0 !left-0'}
+            type="target"
+          ></Handle>
+        }
+        handleRight={
+          <Handle
+            id={toHandleId(data.rightRelationHandleId)}
+            // id="entitySourceRight"
+            position={Position.Right}
+            className={'!rounded-none !bg-transparent !w-full !h-full !border-0 !right-0 !left-0'}
+            type="source"
+          ></Handle>
+        }
+      >
+        {data?.attributes && (
+          <PillDropdown
+            node={node}
+            attributes={data.attributes}
+            attributeEdges={attributeEdges.map((edge) => edge?.attributes)}
+            hovered={hovered}
+            handleBeingDraggedIdx={handleBeingDragged}
+            onHandleMouseDown={onHandleMouseDown}
+          />
+        )}
+      </EntityPill>
+    </div>
+  );
+});
+
+QueryEntityPill.displayName = 'QueryEntityPill';
+
+export default QueryEntityPill;
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx
deleted file mode 100644
index 9e5750185..000000000
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import { querybuilderSlice, schemaSlice, setQuerybuilderGraph, searchResultSlice } from '@graphpolaris/shared/lib/data-access/store';
-
-import { configureStore } from '@reduxjs/toolkit';
-import { Meta } from '@storybook/react';
-import { Provider } from 'react-redux';
-import { QueryBuilder } from '../../../panel';
-import { QueryMultiGraphology } from '@graphpolaris/shared/lib/querybuilder/model/graphology/utils';
-import { QueryElementTypes } from '../../../model';
-import { SchemaUtils } from '@graphpolaris/shared/lib/schema/schema-utils';
-
-const Component: Meta<typeof QueryBuilder> = {
-  component: QueryBuilder,
-  title: 'Querybuilder/Pills/EntityPill',
-  decorators: [(story) => <Provider store={Mockstore}>{story()}</Provider>],
-};
-
-const Mockstore = configureStore({
-  reducer: {
-    querybuilder: querybuilderSlice.reducer,
-    schema: schemaSlice.reducer,
-    searchResults: searchResultSlice.reducer,
-  },
-});
-
-export const Flow = {
-  play: async () => {
-    const dispatch = Mockstore.dispatch;
-
-    const graph = new QueryMultiGraphology();
-    graph.addPill2Graphology({ id: '2', type: QueryElementTypes.Entity, x: 100, y: 100, name: 'Entity Pill' });
-    dispatch(setQuerybuilderGraph(graph.export()));
-  },
-  args: {},
-};
-
-export default Component;
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.stories.tsx
deleted file mode 100644
index 4d38dd883..000000000
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.stories.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import { Meta, StoryObj } from '@storybook/react';
-import EntityFlowElement from './entitypill';
-import { configureStore } from '@reduxjs/toolkit';
-import { Provider } from 'react-redux';
-
-import { querybuilderSlice, schemaSlice, searchResultSlice } from '@graphpolaris/shared/lib/data-access/store';
-import { ReactFlowProvider } from 'reactflow';
-import { EntityData, Handles, QueryElementTypes } from '../../../model';
-
-const Component: Meta<typeof EntityFlowElement> = {
-  title: 'Querybuilder/Pills/EntityPill',
-  component: EntityFlowElement,
-  decorators: [
-    (story) => (
-      <Provider store={Mockstore}>
-        <ReactFlowProvider>{story()}</ReactFlowProvider>
-      </Provider>
-    ),
-  ],
-};
-
-export default Component;
-
-const Mockstore = configureStore({
-  reducer: {
-    querybuilder: querybuilderSlice.reducer,
-    schema: schemaSlice.reducer,
-    searchResults: searchResultSlice.reducer,
-  },
-});
-
-export const Default: StoryObj<{ data: EntityData }> = {
-  args: {
-    data: {
-      name: 'TestEntity',
-      leftRelationHandleId: { nodeId: '1', nodeName: 'string', nodeType: QueryElementTypes.Entity, handleType: Handles.EntityLeft },
-      rightRelationHandleId: { nodeId: '2', nodeName: 'string2', nodeType: QueryElementTypes.Entity, handleType: Handles.EntityRight },
-      selected: false,
-    },
-  },
-};
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
deleted file mode 100644
index 7953484ba..000000000
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-// import { handles } from '@graphpolaris/shared/lib/querybuilder/usecases';
-import { useQuerybuilderGraph } from '@graphpolaris/shared/lib/data-access';
-import React, { useMemo, useState } from 'react';
-import { Position } from 'reactflow';
-import { NodeAttribute, SchemaReactflowEntityNode } from '../../../model';
-import { PillDropdown } from '../../pilldropdown/pilldropdown';
-import { FilterHandle } from '../../FilterHandle';
-
-/**
- * Component to render an entity flow element
- * @param {NodeProps} param0 The data of an entity flow element.
- */
-export const EntityFlowElement = React.memo((node: SchemaReactflowEntityNode) => {
-  const data = node.data;
-  if (!data.leftRelationHandleId) throw new Error('EntityFlowElement: data.leftRelationHandleId is undefined');
-  if (!data.rightRelationHandleId) throw new Error('EntityFlowElement: data.rightRelationHandleId is undefined');
-
-  const graph = useQuerybuilderGraph();
-  const attributeEdges = useMemo(
-    () => graph.edges.filter((edge) => edge.source === node.id && !!edge?.attributes?.sourceHandleData.attributeType),
-    [graph],
-  );
-
-  const [hovered, setHovered] = useState(false);
-  const [handleBeingDragged, setHandleBeingDragged] = useState(-1);
-
-  const onMouseEnter = (event: React.MouseEvent) => {
-    if (!hovered) setHovered(true);
-  };
-
-  const onMouseLeave = (event: React.MouseEvent) => {
-    if (hovered) setHovered(false);
-  };
-
-  const onHandleMouseDown = (attribute: NodeAttribute, i: number, event: React.MouseEvent) => {
-    setHandleBeingDragged(i);
-    window.addEventListener('mouseup', onHandleMouseUp, true);
-  };
-
-  const onHandleMouseUp = () => {
-    setHandleBeingDragged(-1);
-    window.removeEventListener('mouseup', onHandleMouseUp, true);
-  };
-
-  const onConnect = (params: any) => {
-    console.log('EntityPill onConnect', params);
-  };
-
-  return (
-    <div className="p-3 bg-transparent" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
-      <div className={`rounded-sm shadow min-w-[9rem] max-w-[9rem] text-[0.8rem] bg-gradient-to-r pt-1 from-[#FFA952] to-[#D66700]`}>
-        <div className={`pt-1 ${data.selected ? 'bg-secondary-400' : 'bg-secondary-50'}`}>
-          <FilterHandle
-            handle={data.leftRelationHandleId}
-            type="target"
-            position={Position.Left}
-            // className={'!top-8 !left-2 !bg-danger-700 !rounded-none'}
-            // outerClassName={'!bg-blue-700 !rounded-none'}
-            className={'!bg-accent-700'}
-          />
-          <FilterHandle
-            handle={data.rightRelationHandleId}
-            type="source"
-            position={Position.Right}
-            // outerClassName={'!bg-blue-700 !rounded-none'}
-            className={'!bg-accent-700'}
-          />
-          <div className="">
-            <div className="text-center py-1">{data.name}</div>
-          </div>
-          {data?.attributes && (
-            <PillDropdown
-              node={node}
-              attributes={data.attributes}
-              attributeEdges={attributeEdges.map((edge) => edge?.attributes)}
-              hovered={hovered}
-              handleBeingDraggedIdx={handleBeingDragged}
-              onHandleMouseDown={onHandleMouseDown}
-            />
-          )}
-        </div>
-      </div>
-    </div>
-  );
-});
-
-EntityFlowElement.displayName = 'EntityFlowElement';
-
-export default EntityFlowElement;
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/index.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/index.ts
index 8f909017f..6a0697e6f 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/index.ts
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/index.ts
@@ -1 +1 @@
-export * from './entitypill';
+export * from './QueryEntityPill';
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicInput.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/LogicInput.tsx
similarity index 86%
rename from libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicInput.tsx
rename to libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/LogicInput.tsx
index e0087c7ff..6d4b4a852 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicInput.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/LogicInput.tsx
@@ -15,8 +15,7 @@ export const LogicInput = (props: { value: string; type: string; onChange(value:
   return (
     <input
       ref={ref}
-      className="px-0.5 m-2 mt-0 h-5 rounded-sm border-[1px]"
-      style={{ width: props.type === 'string' ? '9rem' : '4rem' }}
+      className="px-1 m-0 mx-1 p-0 h-5 w-full rounded-sm border-[1px]"
       placeholder="empty"
       value={props.value}
       onMouseDownCapture={(e) => {
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/QueryLogicPill.tsx
similarity index 78%
rename from libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.tsx
rename to libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/QueryLogicPill.tsx
index bb3ec3096..d3e6f409b 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/logicpill.tsx
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/logicpill/QueryLogicPill.tsx
@@ -1,13 +1,3 @@
-/**
- * This program has been developed by students from the bachelor Computer Science at
- * Utrecht University within the Software Project course.
- * © Copyright Utrecht University (Department of Information and Computing Sciences)
- */
-
-/* istanbul ignore file */
-/* The comment above was added so the code coverage wouldn't count this file towards code coverage.
- * We do not test components/renderfunctions/styling files.
- * See testing plan for more details.*/
 import { useAppDispatch, useQuerybuilderGraph, useQuerybuilderHash } from '@graphpolaris/shared/lib/data-access';
 import { useEffect, useMemo, useRef, useState } from 'react';
 import { Handle, Position } from 'reactflow';
@@ -15,13 +5,10 @@ import { Handles, LogicNodeAttributes, SchemaReactflowLogicNode, toHandleId } fr
 import { InputNode, InputNodeTypeTypes } from '../../../model/logic/general';
 import { styleHandleMap } from '../../utils';
 import { setQuerybuilderGraphology, toQuerybuilderGraphology } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
-import { LogicInput } from './logicInput';
+import { LogicInput } from './LogicInput';
+import { LogicPill } from '@graphpolaris/shared/lib/components';
 
-/**
- * Component to render an entity flow element
- * @param param0 Data of the flow element.
- */
-export function LogicPill(node: SchemaReactflowLogicNode) {
+export function QueryLogicPill(node: SchemaReactflowLogicNode) {
   const dispatch = useAppDispatch();
   const data = node.data;
   const logic = data.logic;
@@ -64,13 +51,11 @@ export function LogicPill(node: SchemaReactflowLogicNode) {
   }, [node.id]);
 
   return (
-    <div className="rounded-sm shadow h-min-[3rem] text-[13px] bg-gradient-to-r pt-1 from-[#DEB68E] to-[#543719]">
-      <div className={`py-1 h-fit ${data.selected ? 'bg-secondary-400' : 'bg-secondary-50'}`}>
-        {
-          <div className="m-1 mx-2 text-left">
-            {connectionsToLeft.map((e) => e?.attributes?.sourceHandleData.attributeName)}.{output.name}
-          </div>
-        }
+    <LogicPill title="LogicPill">
+      <div className={`py-1 h-fit border-[1px] border-secondary-200 ${data.selected ? 'bg-secondary-400' : 'bg-secondary-100'}`}>
+        <div className="m-1 mx-2 text-left">
+          {connectionsToLeft.map((e) => e?.attributes?.sourceHandleData.attributeName)}.{output.name}
+        </div>
         {node.data.logic.inputs.map((input, i) => {
           return (
             <div key={i}>
@@ -101,7 +86,7 @@ export function LogicPill(node: SchemaReactflowLogicNode) {
                   handleType: Handles.LogicLeft,
                 })}
                 key={input.name + input.type}
-                style={{ top: `${((i + 0.8) / (node.data.logic.inputs.length + 0.6)) * 100}%` }}
+                style={{ top: `${((i + 0.7) / (node.data.logic.inputs.length + 0.4)) * 100}%` }}
                 className={styleHandleMap[input.type] + ''}
               ></Handle>
             </div>
@@ -121,6 +106,6 @@ export function LogicPill(node: SchemaReactflowLogicNode) {
           ></Handle>
         )}
       </div>
-    </div>
+    </LogicPill>
   );
 }
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx
new file mode 100644
index 000000000..9747cd305
--- /dev/null
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/QueryRelationPill.tsx
@@ -0,0 +1,179 @@
+import { memo, useState, useMemo, useEffect } from 'react';
+import { NodeAttribute, RelationNodeAttributes, SchemaReactflowRelationNode, toHandleId } from '../../../model';
+import { useAppDispatch, useQuerybuilderGraph, useQuerybuilderSettings } from '@graphpolaris/shared/lib/data-access';
+import { addWarning } from '@graphpolaris/shared/lib/data-access/store/configSlice';
+import { setQuerybuilderGraphology, toQuerybuilderGraphology } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
+import { PillDropdown } from '../../pilldropdown/PillDropdown';
+import { RelationPill } from '@graphpolaris/shared/lib/components';
+import { Handle, Position } from 'reactflow';
+
+export const QueryRelationPill = memo((node: SchemaReactflowRelationNode) => {
+  const data = node.data;
+  const graph = useQuerybuilderGraph();
+  const graphologyGraph = useMemo(() => toQuerybuilderGraphology(graph), [graph]);
+  const settings = useQuerybuilderSettings();
+  const dispatch = useAppDispatch();
+  const graphologyNodeAttributes = useMemo<RelationNodeAttributes | undefined>(
+    () => (graphologyGraph.hasNode(node.id) ? { ...(graphologyGraph.getNodeAttributes(node.id) as RelationNodeAttributes) } : undefined),
+    [node.id],
+  );
+  const attributeEdges = useMemo(
+    () => graph.edges.filter((edge) => edge.source === node.id && !!edge?.attributes?.sourceHandleData.attributeType),
+    [graph],
+  );
+  const [hovered, setHovered] = useState(false);
+  const [handleBeingDragged, setHandleBeingDragged] = useState(-1);
+
+  const [depth, setDepth] = useState<{ min: number; max: number }>({
+    min: data.depth.min || settings.depth.min,
+    max: data.depth.max || settings.depth.max,
+  });
+
+  // TODO: must do this once design is chosen
+  // const [direction, setDirection] = useState<RelationshipHandleArrowType>('right');
+
+  useEffect(() => {
+    setDepth({ min: data.depth.min || settings.depth.min, max: data.depth.max || settings.depth.max });
+  }, [data.depth]);
+
+  const onNodeUpdated = () => {
+    if (depth.min < 0) {
+      dispatch(addWarning('The minimum depth cannot be smaller than 0'));
+    } else if (depth.max > 99) {
+      dispatch(addWarning('The maximum depth cannot be larger than 99'));
+    } else if (depth.min > depth.max) {
+      dispatch(addWarning('The minimum depth cannot be larger than the maximum depth'));
+    } else {
+      graphologyGraph.setNodeAttribute<any>(node.id, 'depth', depth);
+      dispatch(setQuerybuilderGraphology(graphologyGraph));
+    }
+  };
+
+  // TODO: must do this once design is chosen
+  // const onChangeDirection = () => {
+  //   if (direction === 'right') {
+  //     setDirection('both');
+  //     graphologyGraph.setNodeAttribute<any>(node.id, 'direction', 'both');
+  //   } else {
+  //     setDirection('right');
+  //     graphologyGraph.setNodeAttribute<any>(node.id, 'direction', 'right');
+  //   }
+  //   dispatch(setQuerybuilderGraphology(graphologyGraph));
+  // };
+
+  const calcWidth = (data: number) => {
+    return data.toString().length + 0.5 + 'ch';
+  };
+
+  const onMouseEnter = (event: React.MouseEvent) => {
+    if (!hovered) setHovered(true);
+  };
+
+  const onMouseLeave = (event: React.MouseEvent) => {
+    if (hovered) setHovered(false);
+  };
+
+  const onHandleMouseDown = (attribute: NodeAttribute, i: number, event: React.MouseEvent) => {
+    setHandleBeingDragged(i);
+    window.addEventListener('mouseup', onHandleMouseUp, true);
+  };
+
+  const onHandleMouseUp = () => {
+    setHandleBeingDragged(-1);
+    window.removeEventListener('mouseup', onHandleMouseUp, true);
+  };
+
+  return (
+    <div className="p-3 bg-transparent" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
+      <RelationPill
+        title={
+          <div className="flex flex-row w-full">
+            <span className="flex-grow">{data?.name}</span>
+            <span className="pr-1">
+              <span> [</span>
+              <input
+                className={
+                  'bg-inherit text-center appearance-none mx-0.1 rounded-sm ' +
+                  (depth.min < 0 || depth.min > depth.max ? ' bg-danger-400 ' : '')
+                }
+                style={{ maxWidth: calcWidth(depth.min) }}
+                type="number"
+                min={0}
+                placeholder={'?'}
+                value={depth.min}
+                onChange={(e) => {
+                  setDepth({ ...depth, min: parseInt(e.target.value) });
+                }}
+                onBlur={(e) => {
+                  onNodeUpdated();
+                }}
+                onKeyDown={(e) => {
+                  if (e.key === 'Enter') {
+                    onNodeUpdated();
+                  }
+                }}
+              ></input>
+              <span>..</span>
+              <input
+                className={
+                  'bg-inherit text-center appearance-none mx-0.1 rounded-sm ' +
+                  (depth.max > 99 || depth.min > depth.max ? ' bg-danger-400 ' : '')
+                }
+                style={{ maxWidth: calcWidth(depth.max) }}
+                type="number"
+                min={1}
+                placeholder={'?'}
+                value={depth.max}
+                onChange={(e) => {
+                  setDepth({ ...depth, max: parseInt(e.target.value) });
+                }}
+                onBlur={(e) => {
+                  onNodeUpdated();
+                }}
+                onKeyDown={(e) => {
+                  if (e.key === 'Enter') {
+                    onNodeUpdated();
+                  }
+                }}
+              ></input>
+              <span>]</span>
+            </span>
+          </div>
+        }
+        withHandles="horizontal"
+        handleLeft={
+          <Handle
+            // onDoubleClick={onChangeDirection}
+            id={toHandleId(data.leftEntityHandleId)}
+            position={Position.Left}
+            className={'!rounded-none !bg-transparent !w-full !h-full !border-0 !b-0 !right-0 !left-0'}
+            type="target"
+          ></Handle>
+        }
+        handleRight={
+          <Handle
+            // onDoubleClick={onChangeDirection}
+            id={toHandleId(data.rightEntityHandleId)}
+            position={Position.Right}
+            className={'!rounded-none !bg-transparent !w-full !h-full !border-0 !right-0 !left-0'}
+            type="source"
+          ></Handle>
+        }
+      >
+        {data?.attributes && (
+          <PillDropdown
+            node={node}
+            attributes={data.attributes}
+            attributeEdges={attributeEdges.map((edge) => edge?.attributes)}
+            hovered={hovered}
+            handleBeingDraggedIdx={handleBeingDragged}
+            onHandleMouseDown={onHandleMouseDown}
+          />
+        )}
+      </RelationPill>
+    </div>
+  );
+});
+
+QueryRelationPill.displayName = 'RelationPill';
+export default QueryRelationPill;
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/index.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/index.ts
index deef2e611..c6c75c7e1 100644
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/index.ts
+++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/index.ts
@@ -1 +1 @@
-export * from './relationpill';
+export * from './QueryRelationPill';
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx
deleted file mode 100644
index 5da8f51bc..000000000
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full_reactflow.stories.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import React from 'react';
-import { querybuilderSlice, schemaSlice, setQuerybuilderGraph, searchResultSlice } from '@graphpolaris/shared/lib/data-access/store';
-
-import { configureStore } from '@reduxjs/toolkit';
-import { Meta } from '@storybook/react';
-import { Provider } from 'react-redux';
-import { QueryBuilder } from '../../../panel';
-import { QueryElementTypes, QueryMultiGraphology } from '../../../model';
-
-const Component: Meta<typeof QueryBuilder> = {
-  component: QueryBuilder,
-  title: 'Querybuilder/Pills/relationPill',
-  decorators: [(story) => <Provider store={mockStore}>{story()}</Provider>],
-};
-
-// Mock palette store
-const mockStore = configureStore({
-  reducer: {
-    querybuilder: querybuilderSlice.reducer,
-    schema: schemaSlice.reducer,
-    searchResults: searchResultSlice.reducer,
-  },
-});
-const graph = new QueryMultiGraphology();
-graph.addPill2Graphology({
-  id: '2',
-  type: QueryElementTypes.Relation,
-  x: 140,
-  y: 140,
-  name: 'Relation Pill',
-  depth: { min: 0, max: 1 },
-});
-console.log(graph.export());
-
-mockStore.dispatch(setQuerybuilderGraph(graph.export()));
-
-export const Flow = {
-  args: {},
-};
-
-export default Component;
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-handles.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-handles.tsx
deleted file mode 100644
index d1942fbad..000000000
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-handles.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Handle, HandleType, Position } from 'reactflow';
-import { QueryGraphEdgeHandle } from '../../..';
-import { tailwindColors } from 'config';
-import { FilterHandle } from '../../FilterHandle';
-
-const rightArrow = '0,0 8,5 0,10';
-const leftArrow = '8,0 0,5 8,10';
-const square = '9,9 1,9 1,1 9,1';
-const getArrow = {
-  right: rightArrow,
-  left: leftArrow,
-  both: square,
-};
-export type RelationshipHandleArrowType = 'right' | 'left' | 'both';
-
-type Props = {
-  handle: QueryGraphEdgeHandle;
-  type: HandleType;
-  point: RelationshipHandleArrowType;
-  onDoubleClick?: () => void;
-};
-
-export const LeftHandle = (props: Props) => {
-  return (
-    <FilterHandle
-      handle={props.handle}
-      type={props.type}
-      position={Position.Left}
-      onDoubleClickCapture={(e) => {
-        e.preventDefault();
-        e.stopPropagation();
-        if (props.onDoubleClick) props.onDoubleClick();
-      }}
-    >
-      <svg className="pointer-events-none" height={10} style={{ transform: `translate(3px, 4px)` }}>
-        <polygon points={getArrow[props.point]} className="fill-primary-600" />
-      </svg>
-    </FilterHandle>
-  );
-};
-
-export const RightHandle = (props: Props) => {
-  return (
-    <FilterHandle
-      handle={props.handle}
-      type={props.type}
-      position={Position.Right}
-      onDoubleClickCapture={(e) => {
-        e.preventDefault();
-        e.stopPropagation();
-        if (props.onDoubleClick) props.onDoubleClick();
-      }}
-    >
-      <svg height={10} className="pointer-events-none" style={{ transform: `translate(6px, 4px)` }}>
-        <polygon points={getArrow[props.point]} className="fill-primary-600" />
-      </svg>
-    </FilterHandle>
-  );
-};
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss
deleted file mode 100644
index 2c087a1ef..000000000
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss
+++ /dev/null
@@ -1,119 +0,0 @@
-@import '../../querypills.module.scss';
-
-$height: 10px;
-$width: 325;
-// Relation element
-
-.relation {
-  min-width: $width + px;
-  text-align: center;
-  font-weight: bold;
-  border-left: 3px solid;
-  @apply bg-primary-50;
-  @apply border-l-primary-600;
-  font-size: 13px;
-}
-
-.relationWrapper {
-  display: inherit;
-  width: inherit;
-  align-items: center;
-  justify-content: space-between;
-}
-
-.relationHandleTriangle {
-  border-radius: 0px !important;
-  background: transparent !important;
-  width: 0 !important;
-  height: 0 !important;
-  border-left: 5px solid transparent !important;
-  border-right: 5px solid transparent !important;
-  border-bottom: 8px solid !important;
-  @apply border-b-primary-200 #{!important};
-}
-
-.relationHandleLeft {
-  @extend .relationHandleTriangle;
-  transform: rotate(-90deg) translate(50%, 150%) !important;
-}
-
-.relationHandleRight {
-  @extend .relationHandleTriangle;
-  transform: rotate(90deg) translate(-50%, 150%) !important;
-}
-
-// .relationHandleAttribute {
-//   // SECOND ONE
-//   border-radius: 1px !important;
-//   left: 22.5px !important;
-//   background: rgba(255, 255, 255, 0.6) !important;
-//   transform: rotate(45deg) translate(-68%, 0) scale(0.9) !important;
-//   border-color: rgba(22, 110, 110, 1) !important;
-//   border-width: 1px !important;
-//   transform-origin: center, center;
-// }
-
-// .relationHandleFunction {
-//   // THIRD ONE
-//   left: 39px !important;
-//   background: rgba(255, 255, 255, 0.6) !important;
-//   border-color: rgba(22, 110, 110, 1) !important;
-//   border-width: 1px !important;
-//   transform-origin: center, center;
-// }
-
-.relationDataWrapper {
-  display: flex;
-  width: 100%;
-  justify-content: center;
-
-  .relationHandleFiller {
-    flex: 1 1 0;
-  }
-
-  .relationSpan {
-    float: left;
-    margin-left: 5px;
-  }
-
-  .relationInputHolder {
-    display: flex;
-    float: right;
-    margin-right: 20px;
-    margin-top: 4px;
-    margin-left: 5px;
-    max-width: 80px;
-    border-radius: 2px;
-    align-items: center;
-    max-height: 12px;
-
-    .relationInput {
-      z-index: 1;
-      cursor: text;
-      min-width: 0;
-      max-width: 1.5ch;
-      border: none;
-      background: transparent;
-      text-align: center;
-      font-size: 0.8rem;
-      user-select: none;
-
-      &:focus {
-        outline: none;
-        user-select: none;
-      }
-
-      &::placeholder {
-        outline: none;
-        user-select: none;
-        font-style: italic;
-      }
-    }
-
-    .relationReadonly {
-      cursor: grab !important;
-      user-select: none;
-      font-style: normal !important;
-    }
-  }
-}
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts
deleted file mode 100644
index 432ea72c9..000000000
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-declare const classNames: {
-  readonly handle: 'handle';
-  readonly handle_logic: 'handle_logic';
-  readonly handle_logic_duration: 'handle_logic_duration';
-  readonly handle_logic_datetime: 'handle_logic_datetime';
-  readonly handle_logic_time: 'handle_logic_time';
-  readonly handle_logic_date: 'handle_logic_date';
-  readonly handle_logic_bool: 'handle_logic_bool';
-  readonly handle_logic_float: 'handle_logic_float';
-  readonly handle_logic_int: 'handle_logic_int';
-  readonly handle_logic_string: 'handle_logic_string';
-  readonly 'react-flow__node': 'react-flow__node';
-  readonly selected: 'selected';
-  readonly entityWrapper: 'entityWrapper';
-  readonly hidden: 'hidden';
-  readonly 'react-flow__edges': 'react-flow__edges';
-  readonly 'react-flow__edge-default': 'react-flow__edge-default';
-  readonly handleConnectedFill: 'handleConnectedFill';
-  readonly handleConnectedBorderRight: 'handleConnectedBorderRight';
-  readonly handleConnectedBorderLeft: 'handleConnectedBorderLeft';
-  readonly handleFunction: 'handleFunction';
-  readonly relation: 'relation';
-  readonly relationWrapper: 'relationWrapper';
-  readonly relationHandleTriangle: 'relationHandleTriangle';
-  readonly relationHandleRight: 'relationHandleRight';
-  readonly relationHandleLeft: 'relationHandleLeft';
-  readonly relationDataWrapper: 'relationDataWrapper';
-  readonly relationHandleFiller: 'relationHandleFiller';
-  readonly relationSpan: 'relationSpan';
-  readonly relationInputHolder: 'relationInputHolder';
-  readonly relationInput: 'relationInput';
-  readonly relationReadonly: 'relationReadonly';
-};
-export = classNames;
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx
deleted file mode 100644
index 3fbbdd1c4..000000000
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react';
-import { Meta, StoryObj } from '@storybook/react';
-import RelationPill from './relationpill';
-import { configureStore } from '@reduxjs/toolkit';
-import { Provider } from 'react-redux';
-
-import { querybuilderSlice, schemaSlice, searchResultSlice } from '@graphpolaris/shared/lib/data-access/store';
-import { ReactFlowProvider } from 'reactflow';
-import { RelationData } from '../../../model';
-
-const Component: Meta<typeof RelationPill> = {
-  /* 👇 The title prop is optional.
-   * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
-   * to learn how to generate automatic titles
-   */
-  title: 'Querybuilder/Pills/RelationPill',
-  component: RelationPill,
-  decorators: [
-    (story) => (
-      <Provider store={Mockstore}>
-        <ReactFlowProvider>{story()}</ReactFlowProvider>
-      </Provider>
-    ),
-  ],
-};
-
-export default Component;
-
-// A super-simple mock of a redux store
-const Mockstore = configureStore({
-  reducer: {
-    querybuilder: querybuilderSlice.reducer,
-    schema: schemaSlice.reducer,
-    searchResults: searchResultSlice.reducer,
-  },
-});
-
-export const Default: StoryObj<{ data: RelationData }> = {
-  args: {
-    data: {
-      name: 'TestEntity',
-      collection: 'test',
-      depth: { min: 0, max: 1 },
-      direction: 'right',
-    },
-  },
-};
-
-// Default.decorators = [
-//   (story) => (
-//     <Provider store={Mockstore}>
-//       {story()}
-//     </Provider>
-//   ),
-// ];
diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx
deleted file mode 100644
index 7280803b6..000000000
--- a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx
+++ /dev/null
@@ -1,173 +0,0 @@
-import { memo, useState, useMemo, useEffect } from 'react';
-import { NodeAttribute, RelationNodeAttributes, SchemaReactflowRelationNode } from '../../../model';
-import { useAppDispatch, useQuerybuilderGraph, useQuerybuilderSettings } from '@graphpolaris/shared/lib/data-access';
-import { addWarning } from '@graphpolaris/shared/lib/data-access/store/configSlice';
-import { setQuerybuilderGraphology, toQuerybuilderGraphology } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
-import { LeftHandle, RelationshipHandleArrowType, RightHandle } from './relation-handles';
-import { PillDropdown } from '../../pilldropdown/pilldropdown';
-
-/**
- * Component to render a relation flow element
- * @param { FlowElement<RelationData>} param0 The data of a relation flow element.
- */
-export const RelationPill = memo((node: SchemaReactflowRelationNode) => {
-  const data = node.data;
-  const graph = useQuerybuilderGraph();
-  const graphologyGraph = useMemo(() => toQuerybuilderGraphology(graph), [graph]);
-  const settings = useQuerybuilderSettings();
-  const dispatch = useAppDispatch();
-  const graphologyNodeAttributes = useMemo<RelationNodeAttributes | undefined>(
-    () => (graphologyGraph.hasNode(node.id) ? { ...(graphologyGraph.getNodeAttributes(node.id) as RelationNodeAttributes) } : undefined),
-    [node.id]
-  );
-  const attributeEdges = useMemo(
-    () => graph.edges.filter((edge) => edge.source === node.id && !!edge?.attributes?.sourceHandleData.attributeType),
-    [graph]
-  );
-  const [hovered, setHovered] = useState(false);
-  const [handleBeingDragged, setHandleBeingDragged] = useState(-1);
-
-  const [depth, setDepth] = useState<{ min: number; max: number }>({
-    min: data.depth.min || settings.depth.min,
-    max: data.depth.max || settings.depth.max,
-  });
-
-  const [direction, setDirection] = useState<RelationshipHandleArrowType>('right');
-
-  useEffect(() => {
-    setDepth({ min: data.depth.min || settings.depth.min, max: data.depth.max || settings.depth.max });
-  }, [data.depth]);
-
-  const onNodeUpdated = () => {
-    if (depth.min < 0) {
-      dispatch(addWarning('The minimum depth cannot be smaller than 0'));
-    } else if (depth.max > 99) {
-      dispatch(addWarning('The maximum depth cannot be larger than 99'));
-    } else if (depth.min > depth.max) {
-      dispatch(addWarning('The minimum depth cannot be larger than the maximum depth'));
-    } else {
-      graphologyGraph.setNodeAttribute<any>(node.id, 'depth', depth);
-      dispatch(setQuerybuilderGraphology(graphologyGraph));
-    }
-  };
-
-  const onChangeDirection = () => {
-    if (direction === 'right') {
-      setDirection('both');
-      graphologyGraph.setNodeAttribute<any>(node.id, 'direction', 'both');
-    } else {
-      setDirection('right');
-      graphologyGraph.setNodeAttribute<any>(node.id, 'direction', 'right');
-    }
-    dispatch(setQuerybuilderGraphology(graphologyGraph));
-  };
-
-  const calcWidth = (data: number) => {
-    return data.toString().length + 0.5 + 'ch';
-  };
-
-  const onMouseEnter = (event: React.MouseEvent) => {
-    if (!hovered) setHovered(true);
-  };
-
-  const onMouseLeave = (event: React.MouseEvent) => {
-    if (hovered) setHovered(false);
-  };
-
-  const onHandleMouseDown = (attribute: NodeAttribute, i: number, event: React.MouseEvent) => {
-    setHandleBeingDragged(i);
-    window.addEventListener('mouseup', onHandleMouseUp, true);
-  };
-
-  const onHandleMouseUp = () => {
-    setHandleBeingDragged(-1);
-    window.removeEventListener('mouseup', onHandleMouseUp, true);
-  };
-
-  return (
-    <div className="p-3 bg-transparent" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
-      <div className={`rounded-sm shadow min-w-[8rem] text-[0.8rem] bg-gradient-to-r pt-1 from-[#4893D4] to-[#1A476E]`}>
-        <div className={`py-1 ${data.selected ? 'bg-secondary-400' : 'bg-secondary-50'}`}>
-          <div className="px-10">
-            <div className="text-center py-1">
-              {data?.name}
-              <span>
-                <span> [</span>
-                <input
-                  className={
-                    'bg-inherit text-center appearance-none mx-0.1 rounded-sm ' +
-                    (depth.min < 0 || depth.min > depth.max ? ' bg-danger-400 ' : '')
-                  }
-                  style={{ maxWidth: calcWidth(depth.min) }}
-                  type="number"
-                  min={0}
-                  placeholder={'?'}
-                  value={depth.min}
-                  onChange={(e) => {
-                    setDepth({ ...depth, min: parseInt(e.target.value) });
-                  }}
-                  onBlur={(e) => {
-                    onNodeUpdated();
-                  }}
-                  onKeyDown={(e) => {
-                    if (e.key === 'Enter') {
-                      onNodeUpdated();
-                    }
-                  }}
-                ></input>
-                <span>..</span>
-                <input
-                  className={
-                    'bg-inherit text-center appearance-none mx-0.1 rounded-sm ' +
-                    (depth.max > 99 || depth.min > depth.max ? ' bg-danger-400 ' : '')
-                  }
-                  style={{ maxWidth: calcWidth(depth.max) }}
-                  type="number"
-                  min={1}
-                  placeholder={'?'}
-                  value={depth.max}
-                  onChange={(e) => {
-                    setDepth({ ...depth, max: parseInt(e.target.value) });
-                  }}
-                  onBlur={(e) => {
-                    onNodeUpdated();
-                  }}
-                  onKeyDown={(e) => {
-                    if (e.key === 'Enter') {
-                      onNodeUpdated();
-                    }
-                  }}
-                ></input>
-                <span>]</span>
-              </span>
-            </div>
-          </div>
-          <span>
-            {data.leftEntityHandleId && (
-              <LeftHandle handle={data.leftEntityHandleId} type="target" point={'left'} onDoubleClick={onChangeDirection} />
-            )}
-          </span>
-
-          <span>
-            {data.rightEntityHandleId && (
-              <RightHandle handle={data.rightEntityHandleId} type="source" point={direction} onDoubleClick={onChangeDirection} />
-            )}
-          </span>
-          {data?.attributes && (
-            <PillDropdown
-              node={node}
-              attributes={data.attributes}
-              attributeEdges={attributeEdges.map((edge) => edge?.attributes)}
-              hovered={hovered}
-              handleBeingDraggedIdx={handleBeingDragged}
-              onHandleMouseDown={onHandleMouseDown}
-            />
-          )}
-        </div>
-      </div>
-    </div>
-  );
-});
-
-RelationPill.displayName = 'RelationPill';
-export default RelationPill;
diff --git a/libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.tsx b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx
similarity index 65%
rename from libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.tsx
rename to libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx
index 312a11d05..156337cd6 100644
--- a/libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.tsx
+++ b/libs/shared/lib/querybuilder/pills/pilldropdown/PillDropdown.tsx
@@ -1,10 +1,10 @@
-import { useMemo, ReactNode, ReactElement } from 'react';
+import { useMemo, ReactElement } from 'react';
 import { NodeAttribute, QueryGraphEdges, SchemaReactflowEntityNode, handleDataFromReactflowToDataId, toHandleId } from '../../model';
-import { Position } from 'reactflow';
-import styles from './pilldropdown.module.scss';
-import { FilterHandle } from '../FilterHandle';
+import { Handle, Position } from 'reactflow';
 import { Abc, CalendarToday, Map, Numbers, Place, QuestionMarkOutlined } from '@mui/icons-material';
 import Icon from '@graphpolaris/shared/lib/components/icon';
+import { PillHandle } from '@graphpolaris/shared/lib/components/pills/PillHandle';
+import { pillDropdownPadding } from '@graphpolaris/shared/lib/components/pills/pill.const';
 
 type PillDropdownProps = {
   node: SchemaReactflowEntityNode;
@@ -28,7 +28,7 @@ const IconMap: IconMapType = {
 };
 
 export const PillDropdown = (props: PillDropdownProps) => {
-  const forceOpen = false;
+  const forceOpen = true;
   const openNumbers: number[] = useMemo(() => {
     if (forceOpen || props.hovered) return props.attributes.map((_, i) => i);
 
@@ -48,7 +48,12 @@ export const PillDropdown = (props: PillDropdownProps) => {
 
   return (
     <>
-      <div className={'divide-y divide-secondary-100' + (openNumbers.length > 0 ? 'animate-openmenu ' : 'animate-closemenu ')}>
+      <div
+        className={
+          'border-[1px] border-secondary-200 divide-y divide-secondary-200 ' +
+          (openNumbers.length > 0 ? 'animate-openmenu ' : 'animate-closemenu ')
+        }
+      >
         {openNumbers.map((i) => {
           const attribute = props.attributes[i];
           if (attribute.handleData.attributeName === undefined) {
@@ -57,26 +62,28 @@ export const PillDropdown = (props: PillDropdownProps) => {
 
           return (
             <div
-              className="px-2 py-1 bg-white flex justify-between items-center"
+              className="px-2 py-1 bg-secondary-100 flex justify-between items-center"
               key={(attribute.handleData.attributeName || '') + i}
               onMouseDown={(event: React.MouseEvent) => {
                 props.onHandleMouseDown(attribute, i, event);
               }}
             >
               <p className="truncate text-[0.6rem]">{attribute.handleData.attributeName}</p>
-              {attribute.handleData?.attributeDimension && (
-                // <div className="!text-xs text-secondary-500">{IconMap[attribute.handleData.attributeDimension]}</div>
-                // <div className="!text-xs text-secondary-500">
-                <Icon component={IconMap[attribute.handleData.attributeDimension]} size={16} />
-                // </div>
-              )}
-              <FilterHandle
-                handle={handleDataFromReactflowToDataId(props.node, attribute)}
-                type="source"
-                position={Position.Right}
-                className={styles.handle + ' bg-secondary-500 rounded-full'}
+              {attribute.handleData?.attributeDimension && <Icon component={IconMap[attribute.handleData.attributeDimension]} size={16} />}
+              <PillHandle
+                mr={-pillDropdownPadding}
                 handleTop="auto"
-              ></FilterHandle>
+                position={Position.Right}
+                className={'fill-accent-500 stroke-white'}
+                type="square"
+              >
+                <Handle
+                  id={toHandleId(handleDataFromReactflowToDataId(props.node, attribute))}
+                  type="source"
+                  position={Position.Right}
+                  className={'!rounded-none !bg-transparent !w-full !h-full !right-0 !left-0 !border-0'}
+                ></Handle>
+              </PillHandle>
             </div>
           );
         })}
diff --git a/libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.module.scss b/libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.module.scss
deleted file mode 100644
index 0386e7d22..000000000
--- a/libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.module.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import '../querypills.module.scss';
diff --git a/libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.module.scss.d.ts
deleted file mode 100644
index e478e565f..000000000
--- a/libs/shared/lib/querybuilder/pills/pilldropdown/pilldropdown.module.scss.d.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-declare const classNames: {
-  readonly handle: 'handle';
-  readonly handle_logic: 'handle_logic';
-  readonly handle_logic_duration: 'handle_logic_duration';
-  readonly handle_logic_datetime: 'handle_logic_datetime';
-  readonly handle_logic_time: 'handle_logic_time';
-  readonly handle_logic_date: 'handle_logic_date';
-  readonly handle_logic_bool: 'handle_logic_bool';
-  readonly handle_logic_float: 'handle_logic_float';
-  readonly handle_logic_int: 'handle_logic_int';
-  readonly handle_logic_string: 'handle_logic_string';
-  readonly 'react-flow__node': 'react-flow__node';
-  readonly selected: 'selected';
-  readonly entityWrapper: 'entityWrapper';
-  readonly hidden: 'hidden';
-  readonly 'react-flow__edges': 'react-flow__edges';
-  readonly 'react-flow__edge-default': 'react-flow__edge-default';
-  readonly handleConnectedFill: 'handleConnectedFill';
-  readonly handleConnectedBorderRight: 'handleConnectedBorderRight';
-  readonly handleConnectedBorderLeft: 'handleConnectedBorderLeft';
-  readonly handleFunction: 'handleFunction';
-};
-export = classNames;
diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx
index b396083e7..533948832 100644
--- a/libs/shared/lib/schema/panel/schema.tsx
+++ b/libs/shared/lib/schema/panel/schema.tsx
@@ -7,8 +7,8 @@ import { useSchemaGraph, useSchemaSettings, useSearchResultSchema } from '../../
 import { toSchemaGraphology } from '../../data-access/store/schemaSlice';
 import { NodeEdge } from '../pills/edges/node-edge';
 import { SelfEdge } from '../pills/edges/self-edge';
-import { EntityNode } from '../pills/nodes/entity/entity-node';
-import { RelationNode } from '../pills/nodes/relation/relation-node';
+import { SchemaEntityPill } from '../pills/nodes/entity/SchemaEntityPill';
+import { SchemaRelationPill } from '../pills/nodes/relation/SchemaRelationPill';
 import { SchemaDialog } from './schemaDialog';
 import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material';
 import { AlgorithmToLayoutProvider, AllLayoutAlgorithms, LayoutFactory } from '../../graph-layout';
@@ -25,8 +25,8 @@ const onInit = (reactFlowInstance: ReactFlowInstance) => {
 };
 
 const nodeTypes = {
-  entity: EntityNode,
-  relation: RelationNode,
+  entity: SchemaEntityPill,
+  relation: SchemaRelationPill,
 };
 const edgeTypes = {
   nodeEdge: NodeEdge,
diff --git a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx
similarity index 50%
rename from libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
rename to libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx
index 4e343b817..3afed3f5e 100644
--- a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx
+++ b/libs/shared/lib/schema/pills/nodes/entity/SchemaEntityPill.tsx
@@ -1,29 +1,13 @@
-/**
- * This program has been developed by students from the bachelor Computer Science at
- * Utrecht University within the Software Project course.
- * © Copyright Utrecht University (Department of Information and Computing Sciences)
- */
-
-/* istanbul ignore file */
-/* The comment above was added so the code coverage wouldn't count this file towards code coverage.
- * We do not test components/renderfunctions/styling files.
- * See testing plan for more details.*/
 import React, { useState } from 'react';
-import { Node, Handle, Position, NodeProps } from 'reactflow';
-import styles from './entity.module.scss';
+import { Handle, Position, NodeProps } from 'reactflow';
 import { SchemaReactflowNodeWithFunctions } from '../../../model/reactflow';
 import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder';
 import { SchemaEntityPopup } from './SchemaEntityPopup';
 import { Popup } from '@graphpolaris/shared/lib/components/Popup';
 import { SchemaNode } from '../../../model';
+import { EntityPill } from '@graphpolaris/shared/lib/components';
 
-/**
- * EntityNode is the node that represents the database entities.
- * It has four handles on the top, bottom, right and left to connect edges.
- * It also has a text to display the entity name and show the amount of attributes it contains.
- * @param {NodeProps} param0 The data of an entity flow element.
- */
-export const EntityNode = React.memo(({ id, selected, data }: NodeProps<SchemaReactflowNodeWithFunctions>) => {
+export const SchemaEntityPill = React.memo(({ id, selected, data }: NodeProps<SchemaReactflowNodeWithFunctions>) => {
   const [openPopup, setOpenPopup] = useState(false);
 
   /**
@@ -61,8 +45,9 @@ export const EntityNode = React.memo(({ id, selected, data }: NodeProps<SchemaRe
           <SchemaEntityPopup data={data} onClose={() => setOpenPopup(false)} />
         </Popup>
       )}
+
       <div
-        className={`rounded-sm transition-all duration-150 shadow shadow-dark/10 hover:shadow-md hover:shadow-dark/20 min-w-[4rem] max-w-[8rem] text-[0.8rem] bg-gradient-to-r pt-1 from-[#FFA952] to-[#D66700]`}
+        className="w-fit h-fit"
         onDragStart={(event) => onDragStart(event)}
         onDragStartCapture={(event) => onDragStart(event)}
         onMouseDownCapture={(event) => {
@@ -73,28 +58,31 @@ export const EntityNode = React.memo(({ id, selected, data }: NodeProps<SchemaRe
         }}
         draggable
       >
-        <div className={`py-1 ${selected ? 'bg-secondary-300' : 'bg-secondary-100'}`}>
-          <Handle
-            style={{ pointerEvents: 'none' }}
-            id="entityTargetLeft"
-            position={Position.Left}
-            className={styles.handleTriangleLeft}
-            type="target"
-          ></Handle>
-          <Handle
-            style={{ pointerEvents: 'none' }}
-            id="entitySourceRight"
-            position={Position.Right}
-            className={styles.handleTriangleRight}
-            type="source"
-          ></Handle>
-          <div style={{ pointerEvents: 'none' }} className="p-2 py-1 text-center truncate">
-            <span className="">{id}</span>
-          </div>
-        </div>
+        <EntityPill
+          title={id}
+          withHandles="vertical"
+          handleUp={
+            <Handle
+              style={{ pointerEvents: 'none' }}
+              id="entityTargetLeft"
+              position={Position.Top}
+              className={'!rounded-none !bg-transparent !w-full !h-full !border-0 !b-0 !top-0 !bottom-0'}
+              type="target"
+            ></Handle>
+          }
+          handleDown={
+            <Handle
+              style={{ pointerEvents: 'none' }}
+              id="entitySourceRight"
+              position={Position.Bottom}
+              className={'!rounded-none !bg-transparent !w-full !h-full !border-0 !top-0 !bottom-0'}
+              type="source"
+            ></Handle>
+          }
+        />
       </div>
     </>
   );
 });
 
-EntityNode.displayName = 'EntityNode';
+SchemaEntityPill.displayName = 'EntityNode';
diff --git a/libs/shared/lib/schema/pills/nodes/entity/entity-node.stories.tsx b/libs/shared/lib/schema/pills/nodes/entity/entity-node.stories.tsx
deleted file mode 100644
index 9bf1fe02b..000000000
--- a/libs/shared/lib/schema/pills/nodes/entity/entity-node.stories.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import React from 'react';
-import { Meta } from '@storybook/react';
-
-import { SchemaUtils } from '@graphpolaris/shared/lib/schema/schema-utils';
-import { schemaSlice, setSchema } from '@graphpolaris/shared/lib/data-access/store';
-
-import { configureStore } from '@reduxjs/toolkit';
-import { Provider } from 'react-redux';
-
-import { Schema } from '../../../panel';
-
-const Component: Meta<typeof Schema> = {
-  /* 👇 The title prop is optional.
-   * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
-   * to learn how to generate automatic titles
-   */
-  title: 'Schema/Pills/EntityNode',
-
-  component: Schema,
-  decorators: [
-    (story) => (
-      <Provider store={Mockstore}>
-        <div
-          style={{
-            width: '100%',
-            height: '100vh',
-          }}
-        >
-          {story()}
-        </div>
-      </Provider>
-    ),
-  ],
-};
-
-// Mock the schema and palette store
-const Mockstore = configureStore({
-  reducer: {
-    schema: schemaSlice.reducer,
-  },
-});
-
-export const Default = {
-  play: async () => {
-    const dispatch = Mockstore.dispatch;
-    const schema = SchemaUtils.schemaBackend2Graphology({
-      nodes: [
-        {
-          name: 'NodeDefault',
-          attributes: [
-            { name: 'city', type: 'string' },
-            { name: 'vip', type: 'bool' },
-            { name: 'state', type: 'string' },
-          ],
-        },
-      ],
-      edges: [],
-    });
-
-    dispatch(setSchema(schema.export()));
-  },
-};
-
-export const TooLongTextLabel = {
-  play: async () => {
-    const dispatch = Mockstore.dispatch;
-    const schema = SchemaUtils.schemaBackend2Graphology({
-      nodes: [
-        {
-          name: 'NodeDefaultNodeDefaultNodeDefaultNodeDefaultNodeDefaultNodeDefault',
-          attributes: [
-            { name: 'city', type: 'string' },
-            { name: 'vip', type: 'bool' },
-            { name: 'state', type: 'string' },
-          ],
-        },
-      ],
-      edges: [],
-    });
-
-    dispatch(setSchema(schema.export()));
-  },
-};
-
-export const ShortTextLabel = {
-  play: async () => {
-    const dispatch = Mockstore.dispatch;
-    const schema = SchemaUtils.schemaBackend2Graphology({
-      nodes: [
-        {
-          name: 'N',
-          attributes: [
-            { name: 'city', type: 'string' },
-            { name: 'vip', type: 'bool' },
-            { name: 'state', type: 'string' },
-          ],
-        },
-      ],
-      edges: [],
-    });
-
-    dispatch(setSchema(schema.export()));
-  },
-};
-
-export default Component;
diff --git a/libs/shared/lib/schema/pills/nodes/entity/entity.module.scss b/libs/shared/lib/schema/pills/nodes/entity/entity.module.scss
deleted file mode 100644
index d33f2bd0f..000000000
--- a/libs/shared/lib/schema/pills/nodes/entity/entity.module.scss
+++ /dev/null
@@ -1,35 +0,0 @@
-@import '../schema-pills.module.scss';
-
-.entityNodeAttributesBox {
-  height: 20px;
-  position: absolute;
-  left: 115px;
-  top: -33%;
-  transform: translateX(50%);
-  border-radius: 1px;
-  box-shadow: -1px 2px 5px #888888;
-  text-align: right;
-}
-
-.handleTriangle {
-  border-radius: 0px !important;
-  background: transparent !important;
-  width: 0 !important;
-  height: 0 !important;
-  border-left: 5px solid transparent !important;
-  border-right: 5px solid transparent !important;
-  border-bottom: 8px solid !important;
-  //todo: color
-  //@apply border-b-secondary-300;
-}
-
-.handleTriangleLeft {
-  @extend .handleTriangle;
-  transform: rotate(-90deg) translate(50%, -55%) scale(0.65) !important;
-}
-
-.handleTriangleRight {
-  @extend .handleTriangle;
-  transform: rotate(90deg) translate(-50%, -45%) scale(0.65) !important;
-  @apply border-b-accent-300;
-}
diff --git a/libs/shared/lib/schema/pills/nodes/entity/entity.module.scss.d.ts b/libs/shared/lib/schema/pills/nodes/entity/entity.module.scss.d.ts
deleted file mode 100644
index 6856f4d0e..000000000
--- a/libs/shared/lib/schema/pills/nodes/entity/entity.module.scss.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-declare const classNames: {
-  readonly entityNodeAttributesBox: 'entityNodeAttributesBox';
-  readonly handleTriangle: 'handleTriangle';
-  readonly handleTriangleRight: 'handleTriangleRight';
-  readonly handleTriangleLeft: 'handleTriangleLeft';
-};
-export = classNames;
diff --git a/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx b/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx
similarity index 59%
rename from libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx
rename to libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx
index 5cb30ac7f..c7a79c38a 100644
--- a/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx
+++ b/libs/shared/lib/schema/pills/nodes/relation/SchemaRelationPill.tsx
@@ -1,28 +1,13 @@
-/**
- * This program has been developed by students from the bachelor Computer Science at
- * Utrecht University within the Software Project course.
- * © Copyright Utrecht University (Department of Information and Computing Sciences)
- */
-
-/* istanbul ignore file */
-/* The comment above was added so the code coverage wouldn't count this file towards code coverage.
- * We do not test components/renderfunctions/styling files.
- * See testing plan for more details.*/
 import React, { useState } from 'react';
-import { Node, Handle, Position, NodeProps } from 'reactflow';
-import styles from './relation.module.scss';
-import { SchemaReactflowRelation, SchemaReactflowRelationWithFunctions } from '../../../model/reactflow';
+import { Handle, Position, NodeProps } from 'reactflow';
+import { SchemaReactflowRelationWithFunctions } from '../../../model/reactflow';
 import { QueryElementTypes } from '@graphpolaris/shared/lib/querybuilder';
 import { Popup } from '@graphpolaris/shared/lib/components/Popup';
 import { SchemaRelationshipPopup } from './SchemaRelationshipPopup';
 import { SchemaEdge } from '../../../model';
+import { RelationPill } from '@graphpolaris/shared/lib/components';
 
-/**
- * Relation node component that renders a relation node for the schema.
- * Can be dragged and dropped to the query builder.
- * @param {NodeProps} param0 The data of an entity flow element.
- */
-export const RelationNode = React.memo(({ id, selected, data, ...props }: NodeProps<SchemaReactflowRelationWithFunctions>) => {
+export const SchemaRelationPill = React.memo(({ id, selected, data, ...props }: NodeProps<SchemaReactflowRelationWithFunctions>) => {
   const [openPopup, setOpenPopup] = useState(false);
 
   /**
@@ -67,6 +52,7 @@ export const RelationNode = React.memo(({ id, selected, data, ...props }: NodePr
         </Popup>
       )}
       <div
+        className="w-fit h-fit"
         onDragStart={(event) => onDragStart(event)}
         onDragStartCapture={(event) => onDragStart(event)}
         onMouseDownCapture={(event) => {
@@ -77,32 +63,31 @@ export const RelationNode = React.memo(({ id, selected, data, ...props }: NodePr
         }}
         draggable
       >
-        <div
-          className={`rounded-sm transition-all duration-150 shadow shadow-dark/10 hover:shadow-md hover:shadow-dark/20 min-w-[4rem] max-w-[8rem] text-[0.8rem] bg-gradient-to-r pt-1 from-[#4893D4] to-[#1A476E]`}
-        >
-          <div className={`py-1 ${selected ? 'bg-secondary-300' : 'bg-secondary-100'}`}>
+        <RelationPill
+          title={data.collection}
+          withHandles="vertical"
+          handleUp={
             <Handle
               style={{ pointerEvents: 'none' }}
-              className={styles.handleTriangleTop}
               id="entitySourceLeft"
               position={Position.Top}
+              className={'!rounded-none !bg-transparent !w-full !h-full !border-0 !b-0 !top-0 !bottom-0'}
               type="target"
             ></Handle>
-            <div className="p-2 py-1 text-center truncate">
-              <span className="">{data.collection}</span>
-            </div>
-
+          }
+          handleDown={
             <Handle
-              className={styles.handleTriangleBottom}
               style={{ pointerEvents: 'none' }}
+              id="entitySourceRight"
               position={Position.Bottom}
+              className={'!rounded-none !bg-transparent !w-full !h-full !border-0 !top-0 !bottom-0'}
               type="source"
             ></Handle>
-          </div>
-        </div>
+          }
+        />
       </div>
     </>
   );
 });
 
-RelationNode.displayName = 'RelationNode';
+SchemaRelationPill.displayName = 'RelationNode';
diff --git a/libs/shared/lib/schema/pills/nodes/relation/relation-node.stories.tsx b/libs/shared/lib/schema/pills/nodes/relation/relation-node.stories.tsx
deleted file mode 100644
index c62745747..000000000
--- a/libs/shared/lib/schema/pills/nodes/relation/relation-node.stories.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import React from 'react';
-import { Meta, Story, ComponentStory } from '@storybook/react';
-
-import { SchemaUtils } from '@graphpolaris/shared/lib/schema/schema-utils';
-import { schemaSlice, setSchema } from '@graphpolaris/shared/lib/data-access/store';
-
-import { configureStore } from '@reduxjs/toolkit';
-import { Provider } from 'react-redux';
-
-import { Schema } from '../../../panel';
-
-const Component: Meta<typeof Schema> = {
-  /* 👇 The title prop is optional.
-   * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
-   * to learn how to generate automatic titles
-   */
-  title: 'Schema/Pills/RelationNode',
-
-  component: Schema,
-  decorators: [
-    (story) => (
-      <Provider store={Mockstore}>
-        <div
-          style={{
-            width: '100%',
-            height: '100vh',
-          }}
-        >
-          {story()}
-        </div>
-      </Provider>
-    ),
-  ],
-};
-
-// Mock the schema and palette store
-const Mockstore = configureStore({
-  reducer: {
-    schema: schemaSlice.reducer,
-  },
-});
-
-export const Default = {
-  play: async () => {
-    const dispatch = Mockstore.dispatch;
-    const schema = SchemaUtils.schemaBackend2Graphology({
-      nodes: [
-        {
-          name: 'Node',
-          attributes: [
-            { name: 'city', type: 'string' },
-            { name: 'vip', type: 'bool' },
-            { name: 'state', type: 'string' },
-          ],
-        },
-      ],
-      edges: [
-        {
-          name: 'Node:Node',
-          label: 'Node:Node',
-          from: 'Node',
-          to: 'Node',
-          collection: 'Flights',
-          attributes: [
-            { name: 'arrivalTime', type: 'int' },
-            { name: 'departureTime', type: 'int' },
-          ],
-        },
-      ],
-    });
-
-    dispatch(setSchema(schema.export()));
-  },
-};
-
-export const TooLongLabel = {
-  play: async () => {
-    const dispatch = Mockstore.dispatch;
-    const schema = SchemaUtils.schemaBackend2Graphology({
-      nodes: [
-        {
-          name: 'Node',
-          attributes: [
-            { name: 'city', type: 'string' },
-            { name: 'vip', type: 'bool' },
-            { name: 'state', type: 'string' },
-          ],
-        },
-      ],
-      edges: [
-        {
-          name: 'Node:Node',
-          label: 'Node:Node',
-          from: 'Node',
-          to: 'Node',
-          collection: 'FlightsFlightsFlightsFlightsFlightsFlightsFlightsFlights',
-          attributes: [
-            { name: 'arrivalTime', type: 'int' },
-            { name: 'departureTime', type: 'int' },
-          ],
-        },
-      ],
-    });
-
-    dispatch(setSchema(schema.export()));
-  },
-};
-
-export default Component;
diff --git a/libs/shared/lib/schema/pills/nodes/relation/relation.module.scss b/libs/shared/lib/schema/pills/nodes/relation/relation.module.scss
deleted file mode 100644
index 3ec5fdd99..000000000
--- a/libs/shared/lib/schema/pills/nodes/relation/relation.module.scss
+++ /dev/null
@@ -1,50 +0,0 @@
-@import '../schema-pills.module.scss';
-
-$width: 145;
-
-.handleTriangle {
-  border-radius: 0px !important;
-  background: transparent !important;
-  width: 0 !important;
-  height: 0 !important;
-  border-left: 4px solid transparent !important;
-  border-right: 4px solid transparent !important;
-  border-bottom: 6px solid !important;
-  //todo: color
-  //@apply border-b-secondary-200;
-}
-
-.handleTriangleTop {
-  @extend .handleTriangle;
-  transform: translate(0, -40%) !important;
-}
-
-.handleTriangleBottom {
-  @extend .handleTriangle;
-  transform: rotate(-180deg) translate(0, -40%) !important;
-  @apply border-b-primary-700;
-}
-
-.controls {
-  left: auto !important;
-  bottom: auto !important;
-  top: 10px;
-  right: 20px;
-  width: auto !important;
-}
-
-.exportButton {
-  left: auto !important;
-  bottom: auto !important;
-  top: 10px;
-  right: 20px;
-
-  & svg {
-    transform: scale(1.4);
-  }
-}
-
-.menuText {
-  font-size: small;
-  font-family: Poppins, sans-serif;
-}
diff --git a/libs/shared/lib/schema/pills/nodes/relation/relation.module.scss.d.ts b/libs/shared/lib/schema/pills/nodes/relation/relation.module.scss.d.ts
deleted file mode 100644
index 24d00a2ba..000000000
--- a/libs/shared/lib/schema/pills/nodes/relation/relation.module.scss.d.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-declare const classNames: {
-  readonly handleTriangle: 'handleTriangle';
-  readonly handleTriangleBottom: 'handleTriangleBottom';
-  readonly handleTriangleTop: 'handleTriangleTop';
-  readonly controls: 'controls';
-  readonly exportButton: 'exportButton';
-  readonly menuText: 'menuText';
-};
-export = classNames;
-- 
GitLab