Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • graphpolaris/frontend-v2
  • rijkheere/frontend-v-2-reordering-paoh
2 results
Show changes
Showing
with 416 additions and 285 deletions
...@@ -57,6 +57,7 @@ import { visualizationColors } from '../../../../config/src/colors.ts'; ...@@ -57,6 +57,7 @@ import { visualizationColors } from '../../../../config/src/colors.ts';
}} }}
/> />
{' '}
<ColorItem <ColorItem
title="Extra" title="Extra"
colors={{ colors={{
...@@ -64,6 +65,16 @@ import { visualizationColors } from '../../../../config/src/colors.ts'; ...@@ -64,6 +65,16 @@ import { visualizationColors } from '../../../../config/src/colors.ts';
dark: 'hsl(var(--clr-dark))', dark: 'hsl(var(--clr-dark))',
}} }}
/> />
{' '}
<ColorItem
title="Entities"
colors={{
node: 'hsl(var(--clr-node))',
relation: 'hsl(var(--clr-relation))',
filter: 'hsl(var(--clr-filter))',
}}
/>
</ColorPalette> </ColorPalette>
#### Usage of colors #### Usage of colors
......
.diagonal-lines {
border: 1px solid lightgray;
background:
repeating-linear-gradient(-45deg, transparent, transparent 6px, #eaeaea 6px, #eaeaea 8px),
/* Gray diagonal lines */ linear-gradient(to bottom, transparent, transparent); /* Vertical gradient */
}
import React, { useState } from 'react'; import React from 'react';
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { VisualizationTooltip, CardToolTipVisProps } from '@graphpolaris/shared/lib/components/VisualizationTooltip'; import { VisualizationTooltip, VisualizationTooltipProps } from '@graphpolaris/shared/lib/components/VisualizationTooltip';
import { SchemaPopUp, SchemaPopUpProps } from '@graphpolaris/shared/lib/schema/pills/nodes/SchemaPopUp/SchemaPopUp';
import { NLPopUp, NLPopUpProps } from '@graphpolaris/shared/lib/vis/visualizations/nodelinkvis/components/NLPopup';
const meta: Meta<typeof VisualizationTooltip> = { const meta: Meta<typeof VisualizationTooltip> = {
component: VisualizationTooltip, component: VisualizationTooltip,
...@@ -8,67 +10,96 @@ const meta: Meta<typeof VisualizationTooltip> = { ...@@ -8,67 +10,96 @@ const meta: Meta<typeof VisualizationTooltip> = {
}; };
export default meta; export default meta;
type Story = StoryObj<typeof VisualizationTooltip>;
type CombinedProps = VisualizationTooltipProps & SchemaPopUpProps & NLPopUpProps & { attributes: { name: string; type: string }[] };
type Story = StoryObj<CombinedProps>;
export const SchemaNode: Story = { export const SchemaNode: Story = {
render: (args) => { render: (args) => {
const { name, attributes, colorHeader, numberOfElements } = args;
const data = attributes.reduce(
(acc, attr) => {
if (attr.name && attr.type) {
acc[attr.name] = attr.type;
}
return acc;
},
{} as Record<string, any>,
);
return ( return (
<div className="w-1/4 my-10 m-auto flex items-center justify-center"> <div className="w-1/4 my-10 m-auto flex items-center justify-center">
<VisualizationTooltip {...args} /> <VisualizationTooltip name={name} colorHeader={colorHeader}>
<SchemaPopUp data={data} numberOfElements={numberOfElements} />
</VisualizationTooltip>
</div> </div>
); );
}, },
args: { args: {
type: 'schema',
typeOfSchema: 'node',
name: 'Person', name: 'Person',
data: { attributes: [
born: 'int', { name: 'int', type: 'int' },
name: 'string', { name: 'float', type: 'float' },
description: 'string', { name: 'date', type: 'date' },
}, { name: 'string', type: 'string' },
colorHeader: '#fb7b04', { name: 'boolean', type: 'boolean' },
{ name: 'undefined', type: 'undefined' },
],
colorHeader: 'hsl(var(--clr-node))',
numberOfElements: 1000, numberOfElements: 1000,
}, },
}; };
export const SchemaRelationship: Story = { export const SchemaRelationship: Story = {
render: (args) => { render: (args) => {
const { name, attributes, colorHeader, numberOfElements, connections } = args;
const data = attributes.reduce(
(acc, attr) => {
if (attr.name && attr.type) {
acc[attr.name] = attr.type;
}
return acc;
},
{} as Record<string, any>,
);
return ( return (
<div className="w-1/4 my-10 m-auto flex items-center justify-center"> <div className="w-1/4 my-10 m-auto flex items-center justify-center">
<VisualizationTooltip {...args} /> <VisualizationTooltip name={name} colorHeader={colorHeader}>
<SchemaPopUp data={data} numberOfElements={numberOfElements} connections={connections} />
</VisualizationTooltip>
</div> </div>
); );
}, },
args: { args: {
type: 'schema',
typeOfSchema: 'relationship',
name: 'Directed', name: 'Directed',
data: { attributes: [
born: 'int', { name: 'born', type: 'int' },
name: 'string', { name: 'name', type: 'string' },
description: 'string', { name: 'description', type: 'string' },
imdb: 'string', { name: 'imdb', type: 'string' },
imdbVotes: 'int', { name: 'imdbVotes', type: 'int' },
}, ],
colorHeader: '#0676C1', colorHeader: '#0676C1',
connectedTo: 'Person',
numberOfElements: 231230, numberOfElements: 231230,
connectedFrom: 'Movie', connections: { to: 'Person', from: 'Movie' },
}, },
}; };
export const PopUpVis: Story = { export const NodeLinkPopUp: Story = {
render: (args) => { render: (args) => {
const { name, data, colorHeader } = args;
return ( return (
<div className="w-1/4 my-10 m-auto flex items-center justify-center"> <div className="w-1/4 my-10 m-auto flex items-center justify-center">
<VisualizationTooltip {...args} /> <VisualizationTooltip name={name} colorHeader={colorHeader}>
<NLPopUp data={data} />
</VisualizationTooltip>
</div> </div>
); );
}, },
args: { args: {
name: 'Person', name: 'Person',
type: 'popupvis',
data: { data: {
bio: 'From wikipedia was born in usa from a firefighter father', bio: 'From wikipedia was born in usa from a firefighter father',
name: 'Charlotte Henry', name: 'Charlotte Henry',
......
import React from 'react'; import React, { ReactNode } from 'react';
import { Icon } from '@graphpolaris/shared/lib/components/icon';
import styles from './VisualizationTooltip.module.scss';
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '@graphpolaris/shared/lib/components/tooltip';
import {
CarbonStringInteger,
CarbonStringText,
CarbonCalendar,
CarbonBoolean,
CarbonUndefined,
} from '@graphpolaris/shared/lib/assets/carbonIcons/carbonIcons';
export type CardToolTipVisProps = { export type VisualizationTooltipProps = {
type: string;
name: string; name: string;
data: Record<string, any>;
typeOfSchema?: string;
colorHeader: string; colorHeader: string;
connectedTo?: string; children: ReactNode;
connectedFrom?: string;
maxVisibleItems?: number;
numberOfElements?: number;
}; };
const formatNumber = (number: number) => { export const VisualizationTooltip: React.FC<VisualizationTooltipProps> = ({ name, colorHeader, children }) => {
return number.toLocaleString('de-DE'); // Format number with dots as thousand separators
};
export const VisualizationTooltip: React.FC<CardToolTipVisProps> = ({
type,
name,
data,
colorHeader,
maxVisibleItems = 5,
connectedFrom,
connectedTo,
typeOfSchema,
numberOfElements,
}) => {
const itemsToShow = Object.entries(data).slice(0, maxVisibleItems);
return ( return (
<div className="border-1 border-sec-200 bg-white w-[12rem] -mx-2 -my-1"> <div className="border-1 border-sec-200 bg-light w-[12rem] -mx-2 -my-2">
<div className="flex m-0 justify-start items-stretch border-b border-sec-200 relative"> <div className="flex m-0 justify-start items-stretch border-b border-sec-200 relative">
<div className="left-0 top-0 h-auto w-1.5" style={{ backgroundColor: colorHeader }}></div> <div className="left-0 top-0 h-auto w-1.5" style={{ backgroundColor: colorHeader }}></div>
<div className="px-2.5 py-1 truncate flex"> <div className="px-2.5 py-1 truncate flex">
<Tooltip> <div className={'flex max-w-full'}>
<TooltipTrigger className={'flex max-w-full'}> <span className="text-base font-semibold truncate">{name}</span>
<span className="text-base font-semibold truncate">{name}</span>
</TooltipTrigger>
<TooltipContent side={'top'}>
<span>{name}</span>
</TooltipContent>
</Tooltip>
</div>
{/*
<div className="flex-shrink-0 ml-2">
<Button variantType="secondary" variant="ghost" size="xs" rounded={true} iconComponent={<Close />} onClick={() => {}} />
</div>
*/}
</div>
{type === 'schema' && numberOfElements && (
<div className="px-4 py-1 border-b border-sec-200">
<div className="flex flex-row gap-1 items-center justify-between">
<Icon component="icon-[ic--baseline-numbers]" size={24} />{' '}
<span className="ml-auto text-right">{formatNumber(numberOfElements)}</span>
</div> </div>
</div> </div>
)} </div>
{children}
{type === 'schema' && typeOfSchema === 'relationship' && (
<div className="px-4 py-1 border-b border-sec-200">
<div className="flex flex-row gap-3 items-center justify-between">
<span className="font-semibold">From</span>
<span className="ml-auto text-right">{connectedFrom}</span>
</div>
<div className="flex flex-row gap-1 items-center justify-between">
<span className="font-semibold">To</span>
<span className="ml-auto text-right">{connectedTo}</span>
</div>
</div>
)}
<TooltipProvider delayDuration={300}>
<div className={`px-3 py-1.5 ${data.length > maxVisibleItems ? 'max-h-20 overflow-y-auto' : ''}`}>
{data && Object.keys(data).length === 0 ? (
<div className="flex justify-center items-center h-full">
<span>No attributes</span>
</div>
) : (
Object.entries(data).map(([k, v]) => (
<Tooltip key={k}>
<div className="flex flex-row gap-1 items-center min-h-6">
<span className={`font-semibold truncate ${type === 'schema' ? 'w-[90%]' : 'min-w-[40%]'}`}>{k}</span>
<TooltipTrigger asChild>
<span className="ml-auto text-right truncate grow-1 flex items-center">
{type === 'schema' ? (
<Icon
className="ml-auto text-right flex-shrink-0"
component={
v === 'int' || v === 'float' ? (
<CarbonStringInteger />
) : v === 'string' ? (
<CarbonStringText />
) : v === 'boolean' ? (
<CarbonBoolean />
) : v === 'date' ? (
<CarbonCalendar />
) : v === 'undefined' ? (
<CarbonUndefined />
) : (
<CarbonUndefined />
)
}
color="hsl(var(--clr-sec--400))"
size={24}
/>
) : v !== undefined && (typeof v !== 'object' || Array.isArray(v)) && v != '' ? (
<span className="ml-auto text-right truncate">{typeof v === 'number' ? formatNumber(v) : v.toString()}</span>
) : (
<div className={`ml-auto mt-auto h-4 w-12 ${styles['diagonal-lines']}`}></div>
)}
</span>
</TooltipTrigger>
<TooltipContent side="right">
<div className="max-w-[18rem] break-all line-clamp-6">
{v !== undefined && (typeof v !== 'object' || Array.isArray(v)) && v != '' ? v : 'noData'}
</div>
</TooltipContent>
</div>
</Tooltip>
))
)}
</div>
</TooltipProvider>
</div> </div>
); );
}; };
import React from 'react';
import { StoryObj, Meta } from '@storybook/react';
import { Accordion, AccordionItem, AccordionHead, AccordionBody } from '.';
import { EntityPill, RelationPill } from '../pills';
export default {
title: 'Components/Accordion',
component: Accordion,
decorators: [(Story) => <div className="flex m-5">{Story()}</div>],
} as Meta<typeof Accordion>;
type Story = StoryObj<typeof Accordion>;
export const Default: Story = {
render: () => (
<div className="max-w-md mx-auto my-10">
<Accordion defaultOpenIndex={0} className="w-64">
<AccordionItem>
<AccordionHead>
<EntityPill title="PERSON" />
</AccordionHead>
<AccordionBody>This is the content of Section 1.</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHead>
<EntityPill title="INDICENT" />
</AccordionHead>
<AccordionBody>
<Accordion>
<AccordionItem>
<AccordionHead showArrow={false}>Location info</AccordionHead>
<AccordionBody>Location info settings</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHead showArrow={false}>Color and shape</AccordionHead>
<AccordionBody>Color and shape settings</AccordionBody>
</AccordionItem>
</Accordion>
</AccordionBody>
</AccordionItem>
<AccordionItem>
<AccordionHead>
<RelationPill title="PERSON_OF" />
</AccordionHead>
<AccordionBody>This is the content of Section 3.</AccordionBody>
</AccordionItem>
</Accordion>
</div>
),
};
import React, { useState, ReactElement } from 'react';
import { Button } from '../buttons';
type AccordionProps = {
children: React.ReactNode;
defaultOpenIndex?: number;
defaultOpenAll?: boolean;
className?: string;
};
export function Accordion({ children, defaultOpenIndex, defaultOpenAll = false, className = '' }: AccordionProps) {
const childrenArray = React.Children.toArray(children);
const [openIndexes, setOpenIndexes] = useState<number[]>(() => {
if (defaultOpenAll) {
return childrenArray.map((_, index) => index);
} else if (defaultOpenIndex !== undefined) {
return [defaultOpenIndex];
} else {
return [];
}
});
const toggleIndex = (index: number) => {
setOpenIndexes((currentIndexes) =>
currentIndexes.includes(index) ? currentIndexes.filter((i) => i !== index) : [...currentIndexes, index],
);
};
return (
<div className={`w-full ${className}`}>
{React.Children.map(children, (child, index) => {
if (React.isValidElement(child)) {
return React.cloneElement(child as ReactElement<AccordionItemProps>, {
isOpen: openIndexes.includes(index),
onToggle: () => toggleIndex(index),
});
}
return child;
})}
</div>
);
}
type AccordionItemProps = {
isOpen?: boolean;
onToggle?: () => void;
children: React.ReactNode;
className?: string;
};
export function AccordionItem({ isOpen = false, onToggle, children, className = '' }: AccordionItemProps) {
return (
<div className={`w-full ${className}`}>
{React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child as ReactElement<AccordionHeadProps | AccordionBodyProps>, {
isOpen,
onToggle,
});
}
return child;
})}
</div>
);
}
type AccordionHeadProps = {
isOpen?: boolean;
onToggle?: () => void;
children: React.ReactNode;
showArrow?: boolean;
className?: string;
};
export function AccordionHead({ isOpen = false, onToggle, children, showArrow = true, className = '' }: AccordionHeadProps) {
return (
<div className={`cursor-pointer flex items-center w-full box-border ${className}`} onClick={onToggle}>
{showArrow && (
<Button
size="2xs"
iconComponent={!isOpen ? 'icon-[ic--baseline-arrow-right]' : 'icon-[ic--baseline-arrow-drop-down]'}
variant="ghost"
className="mr-1"
/>
)}
{children}
</div>
);
}
type AccordionBodyProps = {
isOpen?: boolean;
children: React.ReactNode;
className?: string;
};
export function AccordionBody({ isOpen = false, children, className = '' }: AccordionBodyProps) {
return (
<div
className={`overflow-hidden transition-max-height duration-300 ease-in-out w-full box-border ml-2 ${isOpen ? 'max-h-screen' : 'max-h-0'} ${className}`}
>
{isOpen && <div>{children}</div>}
</div>
);
}
import React, { ReactElement, ReactPropTypes, useMemo } from 'react'; import React, { useMemo } from 'react';
import styles from './buttons.module.scss'; import styles from './buttons.module.scss';
import { Icon, Sizes } from '../icon'; import { Icon } from '../icon';
import { forwardRef } from 'react';
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
type ButtonProps = { type ButtonProps = {
......
// BarPlot.stories.tsx // BarPlot.stories.tsx
import React from 'react'; import React from 'react';
import { Meta } from '@storybook/react'; import { Meta } from '@storybook/react';
import AxisComponent, { AxisComponentProps } from '.'; import AxisComponent from '.';
import { scaleLinear } from 'd3'; import { scaleLinear } from 'd3';
export default { export default {
......
import React from 'react'; import React from 'react';
import { Meta } from '@storybook/react'; import { Meta } from '@storybook/react';
import ColorLegendCat, { LegendProps } from '.'; import ColorLegendCat from '.';
export default { export default {
title: 'Visual charts/Charts/ColorLegendCat', title: 'Visual charts/Charts/ColorLegendCat',
......
import { axisBottom, scaleLinear, select } from 'd3'; import { axisBottom, scaleLinear, select } from 'd3';
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
//import { tailwindColors, dataColors, divergenceColors, categoricalColors } from './../../../../../config/src/colors.js';
//import { tailwindColors, dataColors, divergenceColors, categoricalColors } from '@graphpolaris/config/colors.js';
export type ColorLegendSeqDivProps = { export type ColorLegendSeqDivProps = {
colors: string[]; colors: string[];
...@@ -15,9 +13,6 @@ export const ColorLegendSeqDiv = ({ colors, data, tickCount = 5, name }: ColorLe ...@@ -15,9 +13,6 @@ export const ColorLegendSeqDiv = ({ colors, data, tickCount = 5, name }: ColorLe
useEffect(() => { useEffect(() => {
if (!svgRef.current) return; if (!svgRef.current) return;
console.log(colors);
//console.log(divergenceColors.blueRed);
const widthSVG: number = +svgRef.current.clientWidth; const widthSVG: number = +svgRef.current.clientWidth;
const heightSVG: number = +svgRef.current.clientHeight; const heightSVG: number = +svgRef.current.clientHeight;
......
export * from './barplot'; export * from './barplot';
export * from './colorLegendCat'; export * from './colorLegendCat';
export * from './colorLegendSeqDiv'; export * from './colorLegendSeqDiv';
import React from 'react'; import React from 'react';
import { Meta } from '@storybook/react'; import { Meta } from '@storybook/react';
import Scatterplot, { ScatterplotProps, VisualRegionConfig, regionData, DataPoint } from '.'; import Scatterplot, { VisualRegionConfig, regionData, DataPoint } from '.';
import { scaleLinear } from 'd3'; import { scaleLinear } from 'd3';
const Component: Meta<typeof Scatterplot> = { const Component: Meta<typeof Scatterplot> = {
......
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import * as d3 from 'd3'; import * as d3 from 'd3';
//import { VisualRegionConfig, regionData, DataPoint, DataPointXY } from './types';
import * as PIXI from 'pixi.js'; import * as PIXI from 'pixi.js';
export interface ScatterplotProps { export interface ScatterplotProps {
...@@ -69,7 +68,6 @@ const Scatterplot: React.FC<ScatterplotProps> = ({ data, visualConfig, xScale, o ...@@ -69,7 +68,6 @@ const Scatterplot: React.FC<ScatterplotProps> = ({ data, visualConfig, xScale, o
const yScale = d3.scaleLinear().domain([-1, 1]).range([visualConfig.heightMargin, 0]); const yScale = d3.scaleLinear().domain([-1, 1]).range([visualConfig.heightMargin, 0]);
console.log(visualConfig);
// PIXI // PIXI
const app = new PIXI.Application({ const app = new PIXI.Application({
width: visualConfig.width, width: visualConfig.width,
...@@ -103,8 +101,6 @@ const Scatterplot: React.FC<ScatterplotProps> = ({ data, visualConfig, xScale, o ...@@ -103,8 +101,6 @@ const Scatterplot: React.FC<ScatterplotProps> = ({ data, visualConfig, xScale, o
d.gfx = graphics; d.gfx = graphics;
}); });
console.log('DONE SIMULATION');
const svg = d3 const svg = d3
.select(svgRef.current) .select(svgRef.current)
.attr('width', visualConfig.width) .attr('width', visualConfig.width)
......
import React, { useState, useEffect } from 'react'; import React, { useEffect } from 'react';
import { Button } from '../buttons'; // Adjust the import path according to your project structure import { Button } from '../buttons';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip';
import { setTheme, Theme } from '@graphpolaris/shared/lib/data-access/store/configSlice'; import { setTheme, Theme } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { useAppDispatch, useConfig } from '@graphpolaris/shared/lib/data-access/store'; import { useAppDispatch, useConfig } from '@graphpolaris/shared/lib/data-access/store';
...@@ -22,11 +22,8 @@ const ColorMode = () => { ...@@ -22,11 +22,8 @@ const ColorMode = () => {
// Function to toggle the theme // Function to toggle the theme
const toggleTheme = () => { const toggleTheme = () => {
const themes = [ const themes = [Theme.light, Theme.dark];
Theme.light,
Theme.dark,
]
const newTheme = themes[(themes.indexOf(config.theme) + 1) % themes.length]; const newTheme = themes[(themes.indexOf(config.theme) + 1) % themes.length];
dispatch(setTheme(newTheme)); dispatch(setTheme(newTheme));
}; };
......
import React, { useState } from 'react'; import React, { useState } from 'react';
import { DropdownTrigger, DropdownContainer, DropdownItemContainer } from '@graphpolaris/shared/lib/components/dropdowns'; import { DropdownTrigger, DropdownContainer, DropdownItemContainer } from '@graphpolaris/shared/lib/components/dropdowns';
import ColorLegend from '../colorLegend/index.js'; import ColorLegend from '../colorLegend/index.js';
import { DimensionType } from '@graphpolaris/shared/lib/schema/index.js';
import { dataColors } from 'config'; import { dataColors } from 'config';
type TailwindColor = { type TailwindColor = {
...@@ -37,26 +36,22 @@ function generateTailwindColors(dataColors: any) { ...@@ -37,26 +36,22 @@ function generateTailwindColors(dataColors: any) {
type DropdownColorLegendProps = { type DropdownColorLegendProps = {
value: any; value: any;
onChange: (val: any) => void; onChange: (val: any) => void;
dimension?: DimensionType;
distribution?: any;
}; };
export const DropdownColorLegend = ({ value, onChange, dimension, distribution }: DropdownColorLegendProps) => { export const DropdownColorLegend = ({ value, onChange }: DropdownColorLegendProps) => {
const colorStructure = generateTailwindColors(dataColors); const colorStructure = generateTailwindColors(dataColors);
const [selectedColorLegend, setSelectedColorLegend] = useState<any>(null); const [selectedColorLegend, setSelectedColorLegend] = useState<TailwindColor | null>(null);
const [menuOpen, setMenuOpen] = useState<boolean>(false); const [menuOpen, setMenuOpen] = useState<boolean>(false);
const [selectedOption, setSelectedOption] = useState<any>('Select colormap');
const handleOptionClick = (option: string) => { const handleOptionClick = (option: string) => {
setSelectedOption(option); onChange(option);
setSelectedColorLegend(colorStructure[option]); setSelectedColorLegend(colorStructure[option]);
setMenuOpen(false); setMenuOpen(false);
}; };
return ( return (
<div className="w-200 h-200"> <div className="w-200 h-200 relative">
<DropdownContainer> <DropdownContainer open={menuOpen}>
<DropdownTrigger <DropdownTrigger
title={ title={
<div className="flex items-center h-4"> <div className="flex items-center h-4">
...@@ -68,23 +63,26 @@ export const DropdownColorLegend = ({ value, onChange, dimension, distribution } ...@@ -68,23 +63,26 @@ export const DropdownColorLegend = ({ value, onChange, dimension, distribution }
showAxis={selectedColorLegend.showAxis} showAxis={selectedColorLegend.showAxis}
/> />
) : ( ) : (
<p className="ml-2">{selectedOption}</p> <p className="ml-2">{value}</p>
)} )}
</div> </div>
} }
onClick={() => setMenuOpen(!menuOpen)}
/> />
<DropdownItemContainer className="w-60"> <DropdownItemContainer className="absolute w-60 bg-white shadow-lg z-10">
{Object.keys(colorStructure).map((option: any, index) => ( <ul>
<li key={index} onClick={() => handleOptionClick(option)} className="cursor-pointer flex items-center ml-2 h-4 m-2"> {Object.keys(colorStructure).map((option: string, index) => (
<ColorLegend <li key={index} onClick={() => handleOptionClick(option)} className="cursor-pointer flex items-center ml-2 h-4 m-2">
key={index.toString() + '_colorLegend'} <ColorLegend
colors={colorStructure[option].colors} key={index.toString() + '_colorLegend'}
data={colorStructure[option].data} colors={colorStructure[option].colors}
name={colorStructure[option].name} data={colorStructure[option].data}
showAxis={colorStructure[option].showAxis} name={colorStructure[option].name}
/> showAxis={colorStructure[option].showAxis}
</li> />
))} </li>
))}
</ul>
</DropdownItemContainer> </DropdownItemContainer>
</DropdownContainer> </DropdownContainer>
</div> </div>
......
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { ColorPicker } from '.';
const Component: Meta<typeof ColorPicker> = {
title: 'ColorManager/Legend/ColorPicker',
component: ColorPicker,
argTypes: { onChange: { action: 'changed' } },
decorators: [(Story) => <div className="w-52 m-5">{Story()}</div>],
};
export default Component;
type Story = StoryObj<typeof Component>;
export const ColorPickerStory: Story = (args: any) => {
const [value, setValue] = useState<[number, number, number]>([251, 150, 55]);
return <ColorPicker value={value} onChange={setValue} />;
};
ColorPickerStory.args = {};
import React from 'react'; import React from 'react';
import { TwitterPicker } from 'react-color'; import { visualizationColors } from 'config';
import { useFloating, autoUpdate, offset, flip, shift, useInteractions, useClick, FloatingPortal } from '@floating-ui/react'; import { Popover, PopoverTrigger, PopoverContent } from '@graphpolaris/shared/lib/components/layout/Popover';
type Props = { const hexToRgb = (hex: string): [number, number, number] => {
value: any; const r = parseInt(hex.slice(1, 3), 16);
updateValue: (val: [number, number, number]) => void; const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return [r, g, b];
}; };
export default function ColorPicker({ value, updateValue }: Props) { type Props = {
const [open, setOpen] = React.useState(false); value: [number, number, number];
onChange: (val: [number, number, number]) => void;
const { x, y, strategy, context, refs, floatingStyles } = useFloating({ };
placement: 'bottom',
open,
onOpenChange: setOpen,
whileElementsMounted: autoUpdate,
middleware: [offset(5), flip(), shift({ padding: 5 })],
});
const { getReferenceProps, getFloatingProps } = useInteractions([useClick(context)]);
export function ColorPicker({ value, onChange }: Props) {
return ( return (
<> value && (
<div <div>
className="p-1 inline-block cursor-pointer" <Popover>
ref={refs.setReference} <PopoverTrigger
{...getReferenceProps({ onClick={(e) => {
onClick: () => setOpen(!open), e.stopPropagation();
})}
>
<div
className="w-5 h-5"
style={{
backgroundColor: `rgb(${value[0]}, ${value[1]}, ${value[2]})`,
}}
/>
</div>
{open && (
<FloatingPortal>
<div
ref={refs.setFloating}
style={{
position: strategy,
top: y ?? 0,
left: x ?? 0,
...floatingStyles,
}} }}
className="z-10"
{...getFloatingProps()}
> >
<TwitterPicker <div className="w-4 h-4 rounded-sm" style={{ backgroundColor: `rgb(${value[0]}, ${value[1]}, ${value[2]})` }} />
triangle="top-right" </PopoverTrigger>
color={{ r: value[0], g: value[1], b: value[2] }} <PopoverContent>
onChangeComplete={(color) => { <div
console.log(color); className="grid grid-cols-4 gap-2 p-2"
const rgb = color.rgb; onClick={(e) => {
const newValue: [number, number, number] = [rgb.r, rgb.g, rgb.b]; e.stopPropagation();
updateValue(newValue);
setOpen(false);
}} }}
/> >
</div> {visualizationColors.GPCat.colors[14].map((hexColor) => {
</FloatingPortal> const [r, g, b] = hexToRgb(hexColor);
)} return (
</> <div
key={hexColor}
className="w-4 h-4 rounded-sm cursor-pointer"
style={{ backgroundColor: hexColor }}
onClick={(e) => {
e.stopPropagation();
onChange([r, g, b]);
}}
/>
);
})}
</div>
</PopoverContent>
</Popover>
</div>
)
); );
} }
/* eslint-disable react-hooks/rules-of-hooks */
// Dropdown.stories.tsx
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { DropdownContainer, DropdownTrigger, DropdownItemContainer, DropdownItem } from './index';
import { Icon } from '../icon';
const metaDropdown: Meta<typeof DropdownContainer> = {
component: DropdownContainer,
title: 'Components/Dropdown',
};
export default metaDropdown;
type Story = StoryObj<typeof DropdownTrigger>;
export const mainStory: Story = {
render: (args) => {
const [isOpen, setIsOpen] = useState(false);
const handleToggle = () => setIsOpen(!isOpen);
return (
<>
<DropdownContainer {...args} open={isOpen} onOpenChange={setIsOpen}>
<DropdownTrigger onClick={handleToggle} disabled={args.disabled} size="md" title="Dropdown Trigger">
<div className="flex items-center border border-black">
<Icon component="icon-[ic--baseline-arrow-drop-down]" size={16} />
<span className="ml-2">Trigger</span>
</div>
</DropdownTrigger>
<DropdownItemContainer>
<DropdownItem value="item-1" onClick={() => alert('Item 1 clicked')}>
<div className="p-2">Item 1</div>
</DropdownItem>
<DropdownItem value="item-2" onClick={() => alert('Item 2 clicked')}>
<div className="p-2">Item 2</div>
</DropdownItem>
</DropdownItemContainer>
</DropdownContainer>
</>
);
},
args: {
disabled: true,
},
};
import React, { useState, useEffect, useRef, ReactNode } from 'react'; import React, { useState, useRef, ReactNode } from 'react';
import { Icon } from '../icon'; import { Icon } from '../icon';
import { PopoverContent, PopoverTrigger, Popover, PopoverOptions } from '../layout/Popover'; import { PopoverContent, PopoverTrigger, Popover, PopoverOptions } from '../layout/Popover';
...@@ -40,21 +40,29 @@ export function DropdownTrigger({ ...@@ -40,21 +40,29 @@ export function DropdownTrigger({
: variant === 'ghost' : variant === 'ghost'
? 'bg-transparent shadow-none' ? 'bg-transparent shadow-none'
: 'border rounded bg-transparent'; : 'border rounded bg-transparent';
const inner = children || ( const inner = children || (
<div <div
className={`inline-flex w-full truncate justify-between items-center gap-x-1.5 ${variantClass} ${textSizeClass} ${paddingClass} text-secondary-900 shadow-sm hover:bg-secondary-50 disabled:bg-secondary-100 disabled:cursor-not-allowed disabled:text-secondary-400 pl-1 truncate cursor-pointer${className ? ` ${className}` : ''}`} className={`inline-flex w-full truncate justify-between items-center gap-x-1.5 ${variantClass} ${textSizeClass} ${paddingClass} text-secondary-900 shadow-sm ${disabled ? ` cursor-not-allowed text-secondary-400 bg-secondary-100` : 'cursor-pointer'} pl-1 truncate`}
> >
<span className={`text-${size}`}>{title}</span> <span className={`text-${size}`}>{title}</span>
<Icon component="icon-[ic--baseline-arrow-drop-down]" size={16} /> <Icon component="icon-[ic--baseline-arrow-drop-down]" size={16} />
</div> </div>
); );
const handleClick = () => {
if (!disabled && onClick) {
onClick();
}
};
if (popover) { if (popover) {
return <PopoverTrigger onClick={onClick}>{inner}</PopoverTrigger>; return (
<PopoverTrigger className={`${disabled ? 'cursor-not-allowed opacity-50' : ''}`} onClick={handleClick}>
{inner}
</PopoverTrigger>
);
} else } else
return ( return (
<button className="w-full" onClick={onClick}> <button className={`w-full ${disabled ? 'cursor-not-allowed opacity-50' : ''}`} onClick={onClick} disabled={disabled}>
{' '} {' '}
{inner} {inner}
</button> </button>
...@@ -65,23 +73,28 @@ type DropdownItemContainerProps = { ...@@ -65,23 +73,28 @@ type DropdownItemContainerProps = {
// align: string; // align: string;
className?: string; className?: string;
children: ReactNode; children: ReactNode;
disabled?: boolean;
}; };
export const DropdownItemContainer = React.forwardRef<HTMLDivElement, DropdownItemContainerProps>(({ children, className }, ref) => { export const DropdownItemContainer = React.forwardRef<HTMLDivElement, DropdownItemContainerProps>(
if (!children || !React.Children.count(children)) return null; ({ children, className, disabled }, ref) => {
return ( if (!children || !React.Children.count(children)) return null;
<PopoverContent return (
ref={ref} <PopoverContent
className={`bg-light p-1 rounded focus:outline-none shadow-sm`} ref={ref}
role="menu" className={`bg-light p-1 rounded focus:outline-none shadow-sm ${disabled ? 'cursor-not-allowed opacity-50' : ''} ${
aria-orientation="vertical" className || ''
aria-labelledby="menu-button" }`}
tabIndex={-1} role="menu"
> aria-orientation="vertical"
<ul role="none">{children}</ul> aria-labelledby="menu-button"
</PopoverContent> tabIndex={-1}
); >
}); <ul role="none">{children}</ul>
</PopoverContent>
);
},
);
type DropdownItemProps = { type DropdownItemProps = {
value: string; value: string;
......
...@@ -2,7 +2,9 @@ import React, { ReactElement, ReactNode } from 'react'; ...@@ -2,7 +2,9 @@ import React, { ReactElement, ReactNode } from 'react';
import { SVGProps } from 'react'; import { SVGProps } from 'react';
// Define Sizes and IconProps types // Define Sizes and IconProps types
export type Sizes = 12 | 14 | 16 | 20 | 24 | 28 | 32 | 36 | 40; export type Sizes = 8 | 10 | 12 | 14 | 16 | 20 | 24 | 28 | 32 | 36 | 40;
export const sizesArray: Sizes[] = [8, 10, 12, 14, 16, 20, 24, 28, 32, 36, 40];
export type IconProps = SVGProps<SVGSVGElement> & { export type IconProps = SVGProps<SVGSVGElement> & {
component?: ReactNode | ReactElement<any> | string; component?: ReactNode | ReactElement<any> | string;
size?: Sizes; size?: Sizes;
...@@ -31,7 +33,7 @@ export const Icon = ({ component, size = 24, color, ...props }: IconProps) => { ...@@ -31,7 +33,7 @@ export const Icon = ({ component, size = 24, color, ...props }: IconProps) => {
...props, ...props,
}); });
} }
/*
// Check if component is a function (assume it's a custom SVG component) // Check if component is a function (assume it's a custom SVG component)
if (typeof component === 'function') { if (typeof component === 'function') {
// Render the custom SVG component directly // Render the custom SVG component directly
...@@ -41,7 +43,7 @@ export const Icon = ({ component, size = 24, color, ...props }: IconProps) => { ...@@ -41,7 +43,7 @@ export const Icon = ({ component, size = 24, color, ...props }: IconProps) => {
</svg> </svg>
); );
} }
*/
// Default case: render null or fallback // Default case: render null or fallback
console.error('Unsupported icon type'); console.error('Unsupported icon type');
return null; return null;
......