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 467 additions and 43 deletions
......@@ -17,6 +17,7 @@ export const DropdownInputStory: Story = {
type: 'dropdown',
label: 'Select component',
options: ['Option 1', 'Option 2'],
disabled: true,
onChange: (value) => {},
},
};
......@@ -3,7 +3,6 @@ import styles from './inputs.module.scss';
import { DropdownTrigger, DropdownContainer, DropdownItem, DropdownItemContainer } from '../dropdowns';
import Info from '../info';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../tooltip';
import { Popover } from '../layout/Popover';
import { Button } from '../buttons';
type SliderProps = {
......@@ -78,6 +77,11 @@ type BooleanProps = {
value: boolean;
tooltip?: string;
onChange?: (value: boolean) => void;
size?: 'xs' | 'sm' | 'md' | 'xl';
info?: string;
required?: boolean;
className?: string;
inline?: boolean;
};
type RadioProps = {
......@@ -106,7 +110,15 @@ type DropdownProps = {
onChange?: (value: number | string) => void;
};
export type InputProps = TextProps | SliderProps | CheckboxProps | DropdownProps | RadioProps | BooleanProps | NumberProps;
export type InputProps =
| TextProps
| SliderProps
| CheckboxProps
| DropdownProps
| RadioProps
| BooleanProps
| NumberProps
| ToggleSwitchProps;
export const Input = (props: InputProps) => {
switch (props.type) {
......@@ -124,6 +136,8 @@ export const Input = (props: InputProps) => {
return <BooleanInput {...(props as BooleanProps)} />;
case 'number':
return <NumberInput {...(props as NumberProps)} />;
case 'toggle':
return <ToggleSwitchInput {...(props as ToggleSwitchProps)} />;
default:
return null;
}
......@@ -407,23 +421,31 @@ export const CheckboxInput = ({ label, value, options, onChange, tooltip }: Chec
);
};
export const BooleanInput = ({ label, value, onChange, tooltip }: BooleanProps) => {
export const BooleanInput = ({ label, value, onChange, tooltip, info, size, required, className }: BooleanProps) => {
return (
<Tooltip>
<TooltipTrigger>
<label className={`label cursor-pointer w-fit gap-2 px-0 py-1`}>
<span className="text-sm">{label}</span>
<input
type="checkbox"
checked={value}
onChange={(event) => {
if (onChange) {
onChange(event.target.checked);
}
}}
className="checkbox checkbox-xs"
/>
</label>
<TooltipTrigger className={className + 'w-full flex justify-between'}>
{label && (
<label className="label p-0">
<span
className={`text-${size} text-left truncate font-medium text-secondary-700 ${required && "after:content-['*'] after:ml-0.5 after:text-danger-500"}`}
>
{label}
</span>
{info && <Info tooltip={info} />}
</label>
)}
<input
type="checkbox"
checked={value}
onChange={(event) => {
if (onChange) {
onChange(event.target.checked);
}
}}
className="checkbox checkbox-xs"
aria-label={`Toggle ${label}`}
/>
</TooltipTrigger>
{tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip>
......@@ -523,3 +545,42 @@ export const DropdownInput = ({
</Tooltip>
);
};
type ToggleSwitchProps = {
label: string;
type: 'toggle';
value: boolean;
classText?: string;
tooltip?: string;
onChange?: (value: boolean) => void;
};
export const ToggleSwitchInput = ({ label, classText, value, tooltip, onChange }: ToggleSwitchProps) => {
const [isSelected, setIsSelected] = useState(value);
const handleClick = () => {
const newValue = !isSelected;
setIsSelected(newValue);
if (onChange) {
onChange(newValue);
}
};
return (
<Tooltip>
<TooltipTrigger>
<div className="flex items-center space-x-2">
<div
className={`relative flex w-10 h-5 rounded-full cursor-pointer transition-all duration-500 ${isSelected ? 'bg-secondary-800' : 'bg-secondary-300'}`}
onClick={handleClick}
>
<div
className={`absolute top-0 left-0 w-5 h-5 rounded-full items-center bg-white transition-all duration-500 shadow-lg border-1 border-gray ${isSelected ? 'translate-x-full' : 'translate-x-0'} `}
></div>
</div>
<span className={classText}>{label}</span>
</div>
</TooltipTrigger>
{tooltip && <TooltipContent>{tooltip}</TooltipContent>}
</Tooltip>
);
};
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { ToggleSwitchInput } from '.';
const Component: Meta<typeof ToggleSwitchInput> = {
title: 'Components/Inputs',
component: ToggleSwitchInput,
argTypes: { onChange: {} },
decorators: [(Story) => <div className="w-52 m-5">{Story()}</div>],
};
export default Component;
type Story = StoryObj<typeof Component>;
export const ToggleSwitchInputStory: Story = {
args: {
type: 'toggle',
label: 'Toggle Switch component',
value: false,
onChange: (value) => {},
},
};
import * as React from 'react';
import { useEffect, useRef } from 'react';
import {
useFloating,
useClick,
......@@ -143,7 +141,7 @@ export const DialogContent = React.forwardRef<HTMLDivElement, React.HTMLProps<HT
return (
<FloatingPortal>
<FloatingOverlay className="grid place-items-center" lockScroll style={{ backgroundColor: 'rgba(0, 0, 0, 0.4)' }}>
<FloatingOverlay className="grid place-items-center z-50" lockScroll style={{ backgroundColor: 'rgba(0, 0, 0, 0.4)' }}>
<FloatingFocusManager context={context.floatingContext}>
<div
ref={ref}
......
import React, { useEffect, useState } from 'react';
import React from 'react';
import { ControlContainer } from '..';
export type Panel = {
......
......@@ -127,16 +127,20 @@ export function Popover({
interface PopoverTriggerProps {
children: React.ReactNode;
asChild?: boolean;
disabled?: boolean;
}
export const PopoverTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTMLElement> & PopoverTriggerProps>(function PopoverTrigger(
{ children, asChild = false, ...props },
{ children, asChild = false, disabled = false, ...props },
propRef,
) {
const context = usePopoverContext();
const childrenRef = (children as any).ref;
const ref = useMergeRefs([context.data.refs.setReference, propRef, childrenRef]);
const baseClasses = 'data-state:open';
const cursorClass = disabled ? 'cursor-not-allowed' : 'cursor-pointer';
// `asChild` allows the user to pass any element as the anchor
if (asChild && React.isValidElement(children)) {
return React.cloneElement(
......@@ -145,7 +149,8 @@ export const PopoverTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTML
ref,
...props,
...children.props,
'data-state': context.open ? 'open' : 'closed',
className: `${children.props.className || ''} ${cursorClass}`,
//'data-state': context.open ? 'open' : 'closed',
}),
);
}
......@@ -154,6 +159,7 @@ export const PopoverTrigger = React.forwardRef<HTMLElement, React.HTMLProps<HTML
<div
ref={ref}
// The user can style the trigger based on the state
className={`${baseClasses} ${cursorClass} ${props.className || ''}`}
data-state={context.open ? 'open' : 'closed'}
{...context.interactions.getReferenceProps(props)}
>
......
// Popover.stories.tsx
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Popover, PopoverTrigger, PopoverContent, PopoverHeading, PopoverDescription, PopoverClose } from './Popover';
import { Icon } from '../icon';
const metaPopover: Meta<typeof Popover> = {
component: Popover,
title: 'Components/Popover',
};
export default metaPopover;
type Story = StoryObj<typeof Popover>;
export const mainStory: Story = {
render: (args) => {
const [isOpen, setIsOpen] = useState(false);
const size = 'md';
const variant = 'primary';
const className = 'primary';
const handleToggle = () => setIsOpen(!isOpen);
const paddingClass = 'px-2 py-1';
const textSizeClass = 'text-base';
const disabled = true;
const variantClass =
variant === 'primary' || !variant
? 'border bg-light rounded'
: variant === 'ghost'
? 'bg-transparent shadow-none'
: 'border rounded bg-transparent';
return (
<Popover {...args} open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<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 ${disabled ? 'opacity-50 cursor-not-allowed' : ''} ${className || ''}`}
>
<span className={`text-${size}`}>{'Popover trigger'}</span>
<Icon component="icon-[ic--baseline-arrow-drop-down]" size={16} />
</div>
</PopoverTrigger>
<PopoverContent className="p-4 bg-white border rounded shadow-lg">
<PopoverHeading className="text-lg font-bold">Popover Title</PopoverHeading>
<PopoverDescription className="mt-2">This is a description inside the popover.</PopoverDescription>
<PopoverClose className="mt-4 bg-red-500 text-white p-2 rounded">Close</PopoverClose>
</PopoverContent>
</Popover>
);
},
args: {
placement: 'bottom',
modal: false,
onOpenChange: (open: boolean) => console.log(`Popover is ${open ? 'open' : 'closed'}`),
},
};
......@@ -156,7 +156,7 @@ export const EntityPill = React.memo((props: Omit<PillI, 'topColor'> & { withHan
</>
);
return (
<PillContext.Provider value={{ color: 'hsl(29 96 60)' }}>
<PillContext.Provider value={{ color: 'hsl(var(--clr-node))' }}>
<Pill {...props} corner="rounded" handles={handles} />
</PillContext.Provider>
);
......@@ -184,7 +184,7 @@ export const RelationPill = React.memo((props: Omit<PillI, 'topColor'> & { withH
);
return (
<PillContext.Provider value={{ color: '#0676C1' }}>
<PillContext.Provider value={{ color: 'hsl(var(--clr-relation))' }}>
<Pill {...props} corner="diamond" handles={handles} />
</PillContext.Provider>
);
......@@ -198,7 +198,7 @@ export const LogicPill = React.memo((props: Omit<PillI, 'topColor'>) => {
);
return (
<PillContext.Provider value={{ color: '#543719' }}>
<PillContext.Provider value={{ color: 'hsl(var(--clr-filter))' }}>
<Pill {...props} corner="square" handles={handles} />
</PillContext.Provider>
);
......
import { createContext } from 'react';
export const PillContext = createContext({
color: 'hsl(29 96 60)',
color: 'hsl(var(--clr-node))',
});
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { Meta } from '@storybook/react';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
......
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { Meta } 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',
......
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { Meta } from '@storybook/react';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
......
......@@ -2,7 +2,6 @@ import React from 'react';
import { Meta } from '@storybook/react';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import { RelationPill } from '../Pill';
const Component: Meta<typeof RelationPill> = {
......
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { TableUI } from './TableUI'; // Import the TableUI component from the correct path
const metaTableUI: Meta<typeof TableUI> = {
component: TableUI,
title: 'Components/TableUI',
};
export default metaTableUI;
type Story = StoryObj<typeof TableUI>;
interface SampleData {
name: string;
age: string;
email: string;
role: string;
}
const fieldConfigs = [
{ key: 'name', label: 'Name', type: 'text' as const, required: true },
{ key: 'age', label: 'Age', type: 'text' as const, required: true },
{ key: 'email', label: 'Email', type: 'text' as const, required: true },
{ key: 'role', label: 'Role', type: 'dropdown' as const, options: ['Admin', 'User', 'Guest'] },
];
const dropdownOptions = {
role: ['Admin', 'User', 'Guest'],
};
export const MainStory: Story = {
render: (args) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [tableData, setTableData] = useState<SampleData[]>([
{ name: 'John Doe', age: '28', email: 'j.doe@email.com', role: 'Admin' },
{ name: 'Jane Smith', age: '34', email: 'j.smith@email.com', role: 'User' },
]);
const handleDataChange = (updatedData: SampleData[]) => {
setTableData(updatedData);
};
return <TableUI {...args} data={tableData} onDataChange={handleDataChange} />;
},
args: {
fieldConfigs: fieldConfigs,
dropdownOptions: dropdownOptions,
},
};
import React, { useState } from 'react';
import { Button } from '@graphpolaris/shared/lib/components/buttons';
import { Input } from '@graphpolaris/shared/lib/components/inputs';
interface FieldConfig<T> {
key: keyof T;
label: string;
type: 'text' | 'dropdown';
options?: string[];
required?: boolean;
}
interface TableUIProps<T> {
data: T[];
onDataChange: (updatedData: T[]) => void;
fieldConfigs: FieldConfig<T>[];
dropdownOptions: Record<string, string[]>;
}
export const TableUI = <T extends Record<string, any>>({ data, fieldConfigs, dropdownOptions, onDataChange }: TableUIProps<T>) => {
const [editingIndex, setEditingIndex] = useState<number | null>(null);
const [editItem, setEditItem] = useState<T | null>(null);
const handleAddRow = () => {
const newItem = {} as T;
fieldConfigs.forEach((config) => {
newItem[config.key] = config.type === 'dropdown' ? ('' as T[keyof T]) : ('' as T[keyof T]);
});
onDataChange([...data, newItem]);
setEditingIndex(data.length);
setEditItem(newItem);
};
const handleDeleteRow = (index: number) => {
const updatedData = data.filter((_, i) => i !== index);
onDataChange(updatedData);
};
const handleEditRow = (index: number) => {
setEditingIndex(index);
setEditItem(data[index]);
};
const handleSaveEdit = () => {
if (editingIndex !== null && editItem) {
const updatedData = data.map((item, index) => (index === editingIndex ? editItem : item));
onDataChange(updatedData);
setEditingIndex(null);
setEditItem(null);
}
};
const handleCancelEdit = () => {
if (editingIndex === data.length - 1) {
onDataChange(data.slice(0, -1));
}
setEditingIndex(null);
setEditItem(null);
};
const handleInputChange = (key: keyof T, value: string) => {
setEditItem((prev) => (prev ? { ...prev, [key]: value } : null));
};
return (
<div className="mt-2 w-full overflow-x-auto">
<div className="flex justify-end mb-4">
<Button variant="solid" size="md" label="Add Row" onClick={handleAddRow} />
</div>
<table className="min-w-full bg-white border border-gray-300 rounded-md">
<thead>
<tr className="bg-gray-100 border-b">
{fieldConfigs.map((field) => (
<th key={field.key.toString()} className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
{field.label}
</th>
))}
<th className="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody>
{data.map((item, index) => (
<tr key={index} className="border-b rounded-md">
{fieldConfigs.map((field) => (
<td key={field.key.toString()} className="px-6 py-4 text-sm text-gray-700">
{editingIndex === index ? (
field.type === 'dropdown' ? (
<Input
type="dropdown"
label=""
options={dropdownOptions[field.key.toString()] || []}
value={editItem ? editItem[field.key] : ''}
onChange={(value) => handleInputChange(field.key, value.toString())}
/>
) : (
<Input
type="text"
size="xs"
value={editItem ? editItem[field.key] : ''}
required={field.required}
onChange={(e) => handleInputChange(field.key, e)}
/>
)
) : (
item[field.key]
)}
</td>
))}
<td className="px-6 py-4 text-sm text-gray-700">
<div className="flex space-x-2">
{editingIndex === index ? (
<>
<Button variant="solid" size="sm" label="Save" onClick={handleSaveEdit} />
<Button variant="ghost" size="sm" label="Cancel" onClick={handleCancelEdit} />
</>
) : (
<>
<Button
variant="ghost"
size="lg"
iconComponent={'icon-[ic--baseline-delete-outline]'}
className="text-danger-500"
onClick={() => handleDeleteRow(index)}
/>
<Button
variantType="secondary"
variant="ghost"
size="lg"
iconComponent={'icon-[ic--baseline-mode-edit]'}
onClick={() => handleEditRow(index)}
/>
</>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
import React, { MouseEventHandler } from 'react';
import React from 'react';
export const Tabs = (props: { children: React.ReactNode }) => {
return (
......
......@@ -205,7 +205,7 @@ export const TooltipContent = React.forwardRef<
<FloatingPortal>
<div
ref={ref}
className={`z-50 max-w-64 rounded bg-light px-2 py-2 shadow text-xs border border-secondary-200
className={`max-w-64 rounded bg-light px-2 py-2 shadow text-xs border border-secondary-200
text-dark animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0
data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2
data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2${className ? ` ${className}` : ''}`}
......
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { Meta } from '@storybook/react';
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './index';
export default {
......
import React, { useState } from 'react';
import { Button } from '@graphpolaris/shared/lib/components/buttons';
import { useDialogContext } from '@graphpolaris/shared/lib/components/layout/Dialog';
import { Input } from '@graphpolaris/shared/lib/components/inputs';
import { TableUI } from '@graphpolaris/shared/lib/components/tableUI/TableUI';
import { useUsersPolicy } from '@graphpolaris/shared/lib/data-access/store';
import { useDispatch } from 'react-redux';
import { setUsersPolicy, UserPolicy } from '@graphpolaris/shared/lib/data-access/store/authorizationUsersSlice';
interface UserManagementContentProps {
sessionId: string;
onConfirm: (users: { name: string; email: string; type: string }[]) => void;
onClickShareLink: () => void;
}
interface FieldConfig<T> {
key: keyof T;
label: string;
type: 'text' | 'dropdown';
required?: boolean;
}
export const UserManagementContent: React.FC<UserManagementContentProps> = ({ sessionId, onConfirm, onClickShareLink }) => {
const { setOpen } = useDialogContext();
const dispatch = useDispatch();
// !FIXME: This should definited at high level
const optionsTypeUser = ['', 'Creator', 'Viewer'];
const [copiedAccessOption, setCopiedAccessOption] = useState<string>(optionsTypeUser[2]);
const usersPolicy = useUsersPolicy();
// !FIXME: This should be populated from the store
const userFields = [
{ key: 'name', label: 'Name', type: 'text', required: true },
{ key: 'email', label: 'Email', type: 'text', required: true },
{ key: 'type', label: 'Type', type: 'dropdown', required: true },
] as FieldConfig<UserPolicy>[];
const options = {
type: optionsTypeUser,
};
const handleUserChange = (newUsers: UserPolicy[]) => {
dispatch(setUsersPolicy({ users: newUsers }));
};
const handleCancel = () => {
setOpen(false);
};
const handleClickShare = () => {
setOpen(false);
onClickShareLink();
};
return (
<div className="flex flex-col w-[50rem] h-[40rem]">
<div className="flex-grow justify-center p-2 text-sm overflow-hidden text-ellipsis whitespace-nowrap">
<h1 className="flex justify-center font-bold gap-x-1">
<span>Manage Users Sharing of</span> <span className="text-secondary-500">{sessionId}</span>
</h1>
</div>
<div className="flex flex-row items-center justify-center gap-2 mt-4 w-full">
<span>By sharing this link recipient will have </span>
<div>
<Input
type="dropdown"
label=""
value={copiedAccessOption}
options={optionsTypeUser}
onChange={(value) => setCopiedAccessOption(value.toString())}
/>
</div>
<span>access</span>
<Button variant="solid" size="md" label="Copy link" onClick={handleClickShare} />
</div>
<div className="flex items-center my-4">
<hr className="flex-grow border-t border-gray-300" />
<span className="mx-4 text-gray-500">or</span>
<hr className="flex-grow border-t border-gray-300" />
</div>
<div className="flex flex-col items-center flex-grow mt-4">
<TableUI data={usersPolicy.users} fieldConfigs={userFields} dropdownOptions={options} onDataChange={handleUserChange} />
</div>
<div className="flex justify-center p-2 mt-auto">
<div className="flex space-x-4">
<Button variant="outline" size="md" label="Cancel" onClick={handleCancel} />
<Button variantType="primary" size="md" label="Confirm" onClick={() => onConfirm(usersPolicy.users)} />
</div>
</div>
</div>
);
};
......@@ -18,12 +18,7 @@ import {
import { Broker, wsQuerySubscription, wsQueryTranslationSubscription } from '@graphpolaris/shared/lib/data-access/broker';
import { addInfo } from '@graphpolaris/shared/lib/data-access/store/configSlice';
import { allMLTypes, LinkPredictionInstance, setMLResult } from '@graphpolaris/shared/lib/data-access/store/mlSlice';
import {
QueryBuilderText,
attributeShownToggle,
setQueryText,
setQuerybuilderNodes,
} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
import { QueryBuilderText, setQueryText, setQuerybuilderNodes } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice';
import { useEffect } from 'react';
import {
SaveStateI,
......