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';
}}
/>
{' '}
<ColorItem
title="Extra"
colors={{
......@@ -64,6 +65,16 @@ import { visualizationColors } from '../../../../config/src/colors.ts';
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>
#### 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 { 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> = {
component: VisualizationTooltip,
......@@ -8,67 +10,96 @@ const meta: Meta<typeof VisualizationTooltip> = {
};
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 = {
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 (
<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>
);
},
args: {
type: 'schema',
typeOfSchema: 'node',
name: 'Person',
data: {
born: 'int',
name: 'string',
description: 'string',
},
colorHeader: '#fb7b04',
attributes: [
{ name: 'int', type: 'int' },
{ name: 'float', type: 'float' },
{ name: 'date', type: 'date' },
{ name: 'string', type: 'string' },
{ name: 'boolean', type: 'boolean' },
{ name: 'undefined', type: 'undefined' },
],
colorHeader: 'hsl(var(--clr-node))',
numberOfElements: 1000,
},
};
export const SchemaRelationship: Story = {
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 (
<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>
);
},
args: {
type: 'schema',
typeOfSchema: 'relationship',
name: 'Directed',
data: {
born: 'int',
name: 'string',
description: 'string',
imdb: 'string',
imdbVotes: 'int',
},
attributes: [
{ name: 'born', type: 'int' },
{ name: 'name', type: 'string' },
{ name: 'description', type: 'string' },
{ name: 'imdb', type: 'string' },
{ name: 'imdbVotes', type: 'int' },
],
colorHeader: '#0676C1',
connectedTo: 'Person',
numberOfElements: 231230,
connectedFrom: 'Movie',
connections: { to: 'Person', from: 'Movie' },
},
};
export const PopUpVis: Story = {
export const NodeLinkPopUp: Story = {
render: (args) => {
const { name, data, colorHeader } = args;
return (
<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>
);
},
args: {
name: 'Person',
type: 'popupvis',
data: {
bio: 'From wikipedia was born in usa from a firefighter father',
name: 'Charlotte Henry',
......
import React 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';
import React, { ReactNode } from 'react';
export type CardToolTipVisProps = {
type: string;
export type VisualizationTooltipProps = {
name: string;
data: Record<string, any>;
typeOfSchema?: string;
colorHeader: string;
connectedTo?: string;
connectedFrom?: string;
maxVisibleItems?: number;
numberOfElements?: number;
children: ReactNode;
};
const formatNumber = (number: number) => {
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);
export const VisualizationTooltip: React.FC<VisualizationTooltipProps> = ({ name, colorHeader, children }) => {
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="left-0 top-0 h-auto w-1.5" style={{ backgroundColor: colorHeader }}></div>
<div className="px-2.5 py-1 truncate flex">
<Tooltip>
<TooltipTrigger className={'flex max-w-full'}>
<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 className={'flex max-w-full'}>
<span className="text-base font-semibold truncate">{name}</span>
</div>
</div>
)}
{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>
{children}
</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 { Icon, Sizes } from '../icon';
import { forwardRef } from 'react';
import { Icon } from '../icon';
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
type ButtonProps = {
......
// BarPlot.stories.tsx
import React from 'react';
import { Meta } from '@storybook/react';
import AxisComponent, { AxisComponentProps } from '.';
import AxisComponent from '.';
import { scaleLinear } from 'd3';
export default {
......
import React from 'react';
import { Meta } from '@storybook/react';
import ColorLegendCat, { LegendProps } from '.';
import ColorLegendCat from '.';
export default {
title: 'Visual charts/Charts/ColorLegendCat',
......
import { axisBottom, scaleLinear, select } from 'd3';
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 = {
colors: string[];
......@@ -15,9 +13,6 @@ export const ColorLegendSeqDiv = ({ colors, data, tickCount = 5, name }: ColorLe
useEffect(() => {
if (!svgRef.current) return;
console.log(colors);
//console.log(divergenceColors.blueRed);
const widthSVG: number = +svgRef.current.clientWidth;
const heightSVG: number = +svgRef.current.clientHeight;
......
export * from './barplot';
export * from './colorLegendCat';
export * from './colorLegendSeqDiv';
import React from 'react';
import { Meta } from '@storybook/react';
import Scatterplot, { ScatterplotProps, VisualRegionConfig, regionData, DataPoint } from '.';
import Scatterplot, { VisualRegionConfig, regionData, DataPoint } from '.';
import { scaleLinear } from 'd3';
const Component: Meta<typeof Scatterplot> = {
......
import React, { useEffect, useRef } from 'react';
import * as d3 from 'd3';
//import { VisualRegionConfig, regionData, DataPoint, DataPointXY } from './types';
import * as PIXI from 'pixi.js';
export interface ScatterplotProps {
......@@ -69,7 +68,6 @@ const Scatterplot: React.FC<ScatterplotProps> = ({ data, visualConfig, xScale, o
const yScale = d3.scaleLinear().domain([-1, 1]).range([visualConfig.heightMargin, 0]);
console.log(visualConfig);
// PIXI
const app = new PIXI.Application({
width: visualConfig.width,
......@@ -103,8 +101,6 @@ const Scatterplot: React.FC<ScatterplotProps> = ({ data, visualConfig, xScale, o
d.gfx = graphics;
});
console.log('DONE SIMULATION');
const svg = d3
.select(svgRef.current)
.attr('width', visualConfig.width)
......
import React, { useState, useEffect } from 'react';
import { Button } from '../buttons'; // Adjust the import path according to your project structure
import React, { useEffect } from 'react';
import { Button } from '../buttons';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip';
import { setTheme, Theme } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { useAppDispatch, useConfig } from '@graphpolaris/shared/lib/data-access/store';
......@@ -22,11 +22,8 @@ const ColorMode = () => {
// Function to toggle the theme
const toggleTheme = () => {
const themes = [
Theme.light,
Theme.dark,
]
const themes = [Theme.light, Theme.dark];
const newTheme = themes[(themes.indexOf(config.theme) + 1) % themes.length];
dispatch(setTheme(newTheme));
};
......
import React, { useState } from 'react';
import { DropdownTrigger, DropdownContainer, DropdownItemContainer } from '@graphpolaris/shared/lib/components/dropdowns';
import ColorLegend from '../colorLegend/index.js';
import { DimensionType } from '@graphpolaris/shared/lib/schema/index.js';
import { dataColors } from 'config';
type TailwindColor = {
......@@ -37,26 +36,22 @@ function generateTailwindColors(dataColors: any) {
type DropdownColorLegendProps = {
value: any;
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 [selectedColorLegend, setSelectedColorLegend] = useState<any>(null);
const [selectedColorLegend, setSelectedColorLegend] = useState<TailwindColor | null>(null);
const [menuOpen, setMenuOpen] = useState<boolean>(false);
const [selectedOption, setSelectedOption] = useState<any>('Select colormap');
const handleOptionClick = (option: string) => {
setSelectedOption(option);
onChange(option);
setSelectedColorLegend(colorStructure[option]);
setMenuOpen(false);
};
return (
<div className="w-200 h-200">
<DropdownContainer>
<div className="w-200 h-200 relative">
<DropdownContainer open={menuOpen}>
<DropdownTrigger
title={
<div className="flex items-center h-4">
......@@ -68,23 +63,26 @@ export const DropdownColorLegend = ({ value, onChange, dimension, distribution }
showAxis={selectedColorLegend.showAxis}
/>
) : (
<p className="ml-2">{selectedOption}</p>
<p className="ml-2">{value}</p>
)}
</div>
}
onClick={() => setMenuOpen(!menuOpen)}
/>
<DropdownItemContainer className="w-60">
{Object.keys(colorStructure).map((option: any, index) => (
<li key={index} onClick={() => handleOptionClick(option)} className="cursor-pointer flex items-center ml-2 h-4 m-2">
<ColorLegend
key={index.toString() + '_colorLegend'}
colors={colorStructure[option].colors}
data={colorStructure[option].data}
name={colorStructure[option].name}
showAxis={colorStructure[option].showAxis}
/>
</li>
))}
<DropdownItemContainer className="absolute w-60 bg-white shadow-lg z-10">
<ul>
{Object.keys(colorStructure).map((option: string, index) => (
<li key={index} onClick={() => handleOptionClick(option)} className="cursor-pointer flex items-center ml-2 h-4 m-2">
<ColorLegend
key={index.toString() + '_colorLegend'}
colors={colorStructure[option].colors}
data={colorStructure[option].data}
name={colorStructure[option].name}
showAxis={colorStructure[option].showAxis}
/>
</li>
))}
</ul>
</DropdownItemContainer>
</DropdownContainer>
</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 { TwitterPicker } from 'react-color';
import { useFloating, autoUpdate, offset, flip, shift, useInteractions, useClick, FloatingPortal } from '@floating-ui/react';
import { visualizationColors } from 'config';
import { Popover, PopoverTrigger, PopoverContent } from '@graphpolaris/shared/lib/components/layout/Popover';
type Props = {
value: any;
updateValue: (val: [number, number, number]) => void;
const hexToRgb = (hex: string): [number, number, number] => {
const r = parseInt(hex.slice(1, 3), 16);
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) {
const [open, setOpen] = React.useState(false);
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)]);
type Props = {
value: [number, number, number];
onChange: (val: [number, number, number]) => void;
};
export function ColorPicker({ value, onChange }: Props) {
return (
<>
<div
className="p-1 inline-block cursor-pointer"
ref={refs.setReference}
{...getReferenceProps({
onClick: () => setOpen(!open),
})}
>
<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,
value && (
<div>
<Popover>
<PopoverTrigger
onClick={(e) => {
e.stopPropagation();
}}
className="z-10"
{...getFloatingProps()}
>
<TwitterPicker
triangle="top-right"
color={{ r: value[0], g: value[1], b: value[2] }}
onChangeComplete={(color) => {
console.log(color);
const rgb = color.rgb;
const newValue: [number, number, number] = [rgb.r, rgb.g, rgb.b];
updateValue(newValue);
setOpen(false);
<div className="w-4 h-4 rounded-sm" style={{ backgroundColor: `rgb(${value[0]}, ${value[1]}, ${value[2]})` }} />
</PopoverTrigger>
<PopoverContent>
<div
className="grid grid-cols-4 gap-2 p-2"
onClick={(e) => {
e.stopPropagation();
}}
/>
</div>
</FloatingPortal>
)}
</>
>
{visualizationColors.GPCat.colors[14].map((hexColor) => {
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 { PopoverContent, PopoverTrigger, Popover, PopoverOptions } from '../layout/Popover';
......@@ -40,21 +40,29 @@ export function DropdownTrigger({
: variant === 'ghost'
? 'bg-transparent shadow-none'
: 'border rounded bg-transparent';
const inner = children || (
<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>
<Icon component="icon-[ic--baseline-arrow-drop-down]" size={16} />
</div>
);
const handleClick = () => {
if (!disabled && onClick) {
onClick();
}
};
if (popover) {
return <PopoverTrigger onClick={onClick}>{inner}</PopoverTrigger>;
return (
<PopoverTrigger className={`${disabled ? 'cursor-not-allowed opacity-50' : ''}`} onClick={handleClick}>
{inner}
</PopoverTrigger>
);
} else
return (
<button className="w-full" onClick={onClick}>
<button className={`w-full ${disabled ? 'cursor-not-allowed opacity-50' : ''}`} onClick={onClick} disabled={disabled}>
{' '}
{inner}
</button>
......@@ -65,23 +73,28 @@ type DropdownItemContainerProps = {
// align: string;
className?: string;
children: ReactNode;
disabled?: boolean;
};
export const DropdownItemContainer = React.forwardRef<HTMLDivElement, DropdownItemContainerProps>(({ children, className }, ref) => {
if (!children || !React.Children.count(children)) return null;
return (
<PopoverContent
ref={ref}
className={`bg-light p-1 rounded focus:outline-none shadow-sm`}
role="menu"
aria-orientation="vertical"
aria-labelledby="menu-button"
tabIndex={-1}
>
<ul role="none">{children}</ul>
</PopoverContent>
);
});
export const DropdownItemContainer = React.forwardRef<HTMLDivElement, DropdownItemContainerProps>(
({ children, className, disabled }, ref) => {
if (!children || !React.Children.count(children)) return null;
return (
<PopoverContent
ref={ref}
className={`bg-light p-1 rounded focus:outline-none shadow-sm ${disabled ? 'cursor-not-allowed opacity-50' : ''} ${
className || ''
}`}
role="menu"
aria-orientation="vertical"
aria-labelledby="menu-button"
tabIndex={-1}
>
<ul role="none">{children}</ul>
</PopoverContent>
);
},
);
type DropdownItemProps = {
value: string;
......
......@@ -2,7 +2,9 @@ import React, { ReactElement, ReactNode } from 'react';
import { SVGProps } from 'react';
// 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> & {
component?: ReactNode | ReactElement<any> | string;
size?: Sizes;
......@@ -31,7 +33,7 @@ export const Icon = ({ component, size = 24, color, ...props }: IconProps) => {
...props,
});
}
/*
// Check if component is a function (assume it's a custom SVG component)
if (typeof component === 'function') {
// Render the custom SVG component directly
......@@ -41,7 +43,7 @@ export const Icon = ({ component, size = 24, color, ...props }: IconProps) => {
</svg>
);
}
*/
// Default case: render null or fallback
console.error('Unsupported icon type');
return null;
......