diff --git a/libs/shared/lib/components/dropdowns/Dropdown.stories.tsx b/libs/shared/lib/components/dropdowns/Dropdown.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7c2819e13dd50fe37cafa6505c7591ae92604bb2 --- /dev/null +++ b/libs/shared/lib/components/dropdowns/Dropdown.stories.tsx @@ -0,0 +1,48 @@ +/* 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, + }, +}; diff --git a/libs/shared/lib/components/dropdowns/index.tsx b/libs/shared/lib/components/dropdowns/index.tsx index 9d098bf7cb1e16df4c1441b3b6ca23c5b16d97d0..e66cce70ac227068ff00f4f67b4b8449c4db44a1 100644 --- a/libs/shared/lib/components/dropdowns/index.tsx +++ b/libs/shared/lib/components/dropdowns/index.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useRef, ReactNode } from 'react'; import { Icon } from '../icon'; import { PopoverContent, PopoverTrigger, Popover, PopoverOptions } from '../layout/Popover'; +import { space } from 'postcss/lib/list'; export const DropdownContainer = ({ children, ...props }: { children: React.ReactNode } & PopoverOptions) => { return ( @@ -40,21 +41,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 +74,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; diff --git a/libs/shared/lib/components/inputs/dropdown.stories.tsx b/libs/shared/lib/components/inputs/dropdown.stories.tsx index 166f8892fbeeb1da3e0275578038ef80d765d726..8716eeaa0e381f0b980ca0b1f8fc0ff2ac169635 100644 --- a/libs/shared/lib/components/inputs/dropdown.stories.tsx +++ b/libs/shared/lib/components/inputs/dropdown.stories.tsx @@ -17,6 +17,7 @@ export const DropdownInputStory: Story = { type: 'dropdown', label: 'Select component', options: ['Option 1', 'Option 2'], + disabled: true, onChange: (value) => {}, }, }; diff --git a/libs/shared/lib/components/layout/Popover.tsx b/libs/shared/lib/components/layout/Popover.tsx index 3a4f9e063a2466f893a9d4b185fd6d57fd018dc4..27d5a5f9c14f59eaba0fa3614d83debb73f16248 100644 --- a/libs/shared/lib/components/layout/Popover.tsx +++ b/libs/shared/lib/components/layout/Popover.tsx @@ -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)} > diff --git a/libs/shared/lib/components/layout/popover.stories.tsx b/libs/shared/lib/components/layout/popover.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..885debfe606fa96400ed2d1bae434a5a6943ccc5 --- /dev/null +++ b/libs/shared/lib/components/layout/popover.stories.tsx @@ -0,0 +1,57 @@ +// 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'; // Adjust the path based on your component location +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'}`), + }, +};