diff --git a/apps/design-system/src/subjects/views/connectors/connectors-ref.tsx b/apps/design-system/src/subjects/views/connectors/connectors-ref.tsx index 6f1e7daa68..fa0dbcd889 100644 --- a/apps/design-system/src/subjects/views/connectors/connectors-ref.tsx +++ b/apps/design-system/src/subjects/views/connectors/connectors-ref.tsx @@ -4,7 +4,7 @@ import { getHarnessConnectorDefinition, harnessConnectors } from '@utils/connect import { noop, useTranslationStore } from '@utils/viewUtils' import { InputFactory } from '@harnessio/forms' -import { Button, Drawer, FormSeparator, Spacer, Text } from '@harnessio/ui/components' +import { Button, ButtonGroup, Drawer, FormSeparator, Spacer, Text } from '@harnessio/ui/components' import { ArrayFormInput, BooleanFormInput, @@ -118,8 +118,8 @@ export const ConnectorsRefPage = ({ onSelectConnector={() => setIsConnectorSelected(true)} setConnectorEntity={setConnectorEntity} /> - - + + {!!connectorEntity && ( + Connectors - setIsDrawerOpen(false)} srOnly /> - + Choose type @@ -186,12 +185,14 @@ export const ConnectorsRefPage = ({ {renderConnectorContent()} - + - - + + + + diff --git a/apps/design-system/src/subjects/views/connectors/connectors.tsx b/apps/design-system/src/subjects/views/connectors/connectors.tsx index dbdef395e5..21142de88e 100644 --- a/apps/design-system/src/subjects/views/connectors/connectors.tsx +++ b/apps/design-system/src/subjects/views/connectors/connectors.tsx @@ -106,7 +106,7 @@ const ConnectorsListPageContent = (): JSX.Element => { errorData={{ errors: [{ reason: 'Unexpected Error', message: 'Bad credentials' }] }} /> - + { setConnectorEntity={setConnectorEntity} requestClose={onCloseConnectorDrawer} isDrawer - /> - - - {!!connectorEntity && ( - setIsConnectorSelected(false)} - // onFormSubmit={handleFormSubmit} - getConnectorDefinition={getHarnessConnectorDefinition} - inputComponentFactory={inputComponentFactory} - intent={intent} - isDrawer - /> - )} - - + > + + + {!!connectorEntity && ( + setIsConnectorSelected(false)} + // onFormSubmit={handleFormSubmit} + getConnectorDefinition={getHarnessConnectorDefinition} + inputComponentFactory={inputComponentFactory} + intent={intent} + isDrawer + /> + )} + + + - + {!!connectorEntity && ( ( - - + + Delegate selector - setOpen(false)} srOnly /> + {stepNode} - + - + ({ logs })} @@ -99,7 +99,7 @@ export function StepNodeComponent({ onDownload={() => {}} onEdit={() => {}} /> - + ) @@ -131,23 +131,25 @@ export function ApprovalStepNodeComponent({ } return ( - + {approvalNode} - -
- - Approval - Approve/Reject step execution - -
+ + + Approval + Approve/Reject step execution + + + -
-
+ + - - - + + + + +
diff --git a/apps/design-system/src/subjects/views/secrets/secrets.tsx b/apps/design-system/src/subjects/views/secrets/secrets.tsx index 4cd02d5d6a..b8cb3cf3ef 100644 --- a/apps/design-system/src/subjects/views/secrets/secrets.tsx +++ b/apps/design-system/src/subjects/views/secrets/secrets.tsx @@ -4,7 +4,7 @@ import { secretsFormDefinition } from '@utils/secrets/secrets-form-schema' import { noop, useTranslationStore } from '@utils/viewUtils' import { InputFactory } from '@harnessio/forms' -import { Button, Drawer, FormSeparator, Spacer, Text } from '@harnessio/ui/components' +import { Button, ButtonGroup, Drawer, FormSeparator, Spacer, Text } from '@harnessio/ui/components' import { ArrayFormInput, BooleanFormInput, @@ -165,13 +165,12 @@ export const SecretsPage = ({ return ( <> - + Secret - setIsDrawerOpen(false)} srOnly /> - + Choose type @@ -180,12 +179,14 @@ export const SecretsPage = ({ {renderSecretContent()} - + - - + + + + diff --git a/apps/design-system/src/subjects/views/unified-pipeline-studio/unified-pipeline-studio.tsx b/apps/design-system/src/subjects/views/unified-pipeline-studio/unified-pipeline-studio.tsx index bf705af021..bb198a53ed 100644 --- a/apps/design-system/src/subjects/views/unified-pipeline-studio/unified-pipeline-studio.tsx +++ b/apps/design-system/src/subjects/views/unified-pipeline-studio/unified-pipeline-studio.tsx @@ -45,18 +45,16 @@ const PipelineStudioViewWrapper = () => { view={view} onRun={() => setRunPipelineOpen(true)} /> - { if (!isOpen) { setRunPipelineOpen(false) } }} - direction="right" > setRunPipelineOpen(false)} /> - + ) } diff --git a/apps/portal/src/content/docs/components/drawer.mdx b/apps/portal/src/content/docs/components/drawer.mdx index d9a7d10882..23ff1a2a85 100644 --- a/apps/portal/src/content/docs/components/drawer.mdx +++ b/apps/portal/src/content/docs/components/drawer.mdx @@ -4,57 +4,88 @@ description: Drawer component with stacking beta: true --- -The Drawer component works similar to a Sheet component, but allows animated stacking as well. +The `Drawer` component provides a way to display content in a panel that slides in from the edge of the screen. Supports multiple configuration options, including direction (left, right, top, bottom), size constraints, and nested drawers for building complex, multi-level interfaces. + +It is composed of several subcomponents such as `Drawer.Root`, `Drawer.Trigger`, `Drawer.Content`, `Drawer.Header`, `Drawer.Tagline`, `Drawer.Title`, `Drawer.Description`, `Drawer.Body`, `Drawer.Footer` and `Drawer.Close` to offer a structured and customizable interface. import { DocsPage } from "@/components/docs-page"; - - - - Run Step - - -
- - - - - - Create Connector - - -
- - - - - - Create Secret - Shhh. This is a secret. - - - -
-
-
-
-
-
-
-
-
-
`} + code={` + + + + + + + + tagline + Title + + Description + + + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at sagittis justo. + Integer vehicula turpis at justo gravida, at convallis lorem tincidunt. +

+

+ Suspendisse potenti. Praesent in vestibulum lacus. Cras feugiat eros at ex cursus, + vel consequat enim mattis. Quisque nec magna ac leo tempor scelerisque. +

+

+ Nullam tristique, justo vitae laoreet pretium, nisl nunc facilisis nulla, non porta erat sapien in justo. + Sed nec massa nec turpis interdum volutpat. Morbi sed justo ac leo sagittis tincidunt. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at sagittis justo. + Integer vehicula turpis at justo gravida, at convallis lorem tincidunt. +

+

+ Suspendisse potenti. Praesent in vestibulum lacus. Cras feugiat eros at ex cursus, + vel consequat enim mattis. Quisque nec magna ac leo tempor scelerisque. +

+

+ Nullam tristique, justo vitae laoreet pretium, nisl nunc facilisis nulla, non porta erat sapien in justo. + Sed nec massa nec turpis interdum volutpat. Morbi sed justo ac leo sagittis tincidunt. +

+

+ Suspendisse potenti. Praesent in vestibulum lacus. Cras feugiat eros at ex cursus, + vel consequat enim mattis. Quisque nec magna ac leo tempor scelerisque. +

+

+ Nullam tristique, justo vitae laoreet pretium, nisl nunc facilisis nulla, non porta erat sapien in justo. + Sed nec massa nec turpis interdum volutpat. Morbi sed justo ac leo sagittis tincidunt. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at sagittis justo. + Integer vehicula turpis at justo gravida, at convallis lorem tincidunt. +

+

+ Suspendisse potenti. Praesent in vestibulum lacus. Cras feugiat eros at ex cursus, + vel consequat enim mattis. Quisque nec magna ac leo tempor scelerisque. +

+

+ Nullam tristique, justo vitae laoreet pretium, nisl nunc facilisis nulla, non porta erat sapien in justo. + Sed nec massa nec turpis interdum volutpat. Morbi sed justo ac leo sagittis tincidunt. +

+
+ + + + + + + + + +
+
+ +`} /> ## Usage @@ -65,20 +96,628 @@ import { Drawer } from '@harnessio/ui/components' //... return ( - - Open - - - Are you absolutely sure? - This action cannot be undone. - - - - - + + + + + + + + Section + Title of the drawer + Description of the drawer + + + + Content of the drawer goes here + + + + + + + + + Title + + + + Content + + + + Footer + + + + + + + + + - + + + ) ``` + +## Anatomy + +All parts of the `Drawer` component can be imported and composed as required. + +```typescript jsx + + + + + + + + + + + + + + +``` + +## Nested drawers + +Nested drawers can be implemented by placing a `Drawer.Root` inside the parent `Drawer.Content`, and adding the `nested` prop to the nested root. + + + + + + + + + Title + + Description + + + + + + + + + + + + Title + + + + + + + + + + + Title + + + + + + + + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at sagittis justo. + Integer vehicula turpis at justo gravida, at convallis lorem tincidunt. +

+

+ Suspendisse potenti. Praesent in vestibulum lacus. Cras feugiat eros at ex cursus, + vel consequat enim mattis. Quisque nec magna ac leo tempor scelerisque. +

+

+ Nullam tristique, justo vitae laoreet pretium, nisl nunc facilisis nulla, non porta erat sapien in justo. + Sed nec massa nec turpis interdum volutpat. Morbi sed justo ac leo sagittis tincidunt. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at sagittis justo. + Integer vehicula turpis at justo gravida, at convallis lorem tincidunt. +

+

+ Suspendisse potenti. Praesent in vestibulum lacus. Cras feugiat eros at ex cursus, + vel consequat enim mattis. Quisque nec magna ac leo tempor scelerisque. +

+

+ Nullam tristique, justo vitae laoreet pretium, nisl nunc facilisis nulla, non porta erat sapien in justo. + Sed nec massa nec turpis interdum volutpat. Morbi sed justo ac leo sagittis tincidunt. +

+

+ Suspendisse potenti. Praesent in vestibulum lacus. Cras feugiat eros at ex cursus, + vel consequat enim mattis. Quisque nec magna ac leo tempor scelerisque. +

+

+ Nullam tristique, justo vitae laoreet pretium, nisl nunc facilisis nulla, non porta erat sapien in justo. + Sed nec massa nec turpis interdum volutpat. Morbi sed justo ac leo sagittis tincidunt. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at sagittis justo. + Integer vehicula turpis at justo gravida, at convallis lorem tincidunt. +

+

+ Suspendisse potenti. Praesent in vestibulum lacus. Cras feugiat eros at ex cursus, + vel consequat enim mattis. Quisque nec magna ac leo tempor scelerisque. +

+

+ Nullam tristique, justo vitae laoreet pretium, nisl nunc facilisis nulla, non porta erat sapien in justo. + Sed nec massa nec turpis interdum volutpat. Morbi sed justo ac leo sagittis tincidunt. +

+
+ + + + + + +
+
+ +`} +/> + +## Drawer sizes + +The `Drawer.Content` component accepts a `size` prop to control its width (or height, depending on `direction`). Supported values are: `sm`, `md`, and `lg`. + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + + + + + + + + +`} +/> + +## Drawer opening direction + +The `Drawer.Root` component accepts a `direction` prop to control which side the drawer opens from. Supported values are: `right`, `left`, `top`, and `bottom`. + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + + + + + + + + + + + + + + + Title + + + + + + + + + + + + + + + +`} +/> + +## API Reference + +### `Root` + +Contains [`Drawer.Trigger`](#trigger) and [`Drawer.Content`](#content) components. + + void", + defaultValue: "undefined", + }, + { + name: "modal", + description: + "The modality of the dialog. When set to true, interaction with outside elements will be disabled and only dialog content will be visible to screen readers.", + required: false, + value: "boolean", + defaultValue: "true", + }, + { + name: "container", + description: "Specify a container element to portal the drawer into.", + required: false, + value: "HTMLElement", + defaultValue: "document.body", + }, + { + name: "direction", + description: "Direction the drawer will originate from.", + required: false, + value: "'right' | 'left' | 'top' | 'bottom'", + defaultValue: "right", + }, + { + name: "onAnimationEnd", + description: + "Gets triggered after the open or close animation ends, it receives an open argument with the open state of the drawer by the time the function was triggered.", + required: false, + value: "(open: boolean) => void", + defaultValue: "undefined", + }, + { + name: "dismissible", + description: + "When false dragging, clicking outside, pressing esc, etc. will not close the drawer. Use this in combination with the open prop, otherwise you won't be able to open/close the drawer.", + required: false, + value: "boolean", + defaultValue: "true", + }, + { + name: "handleOnly", + description: "When true dragging will only be possible by the handle.", + required: false, + value: "boolean", + defaultValue: "false", + }, + { + name: "nested", + description: "Whether the drawer is nested in another drawer.", + required: false, + value: "boolean", + defaultValue: "false", + }, + { + name: "repositionInputs", + description: + "When true will reposition inputs rather than scroll then into view if the keyboard is in the way. Setting it to false will fall back to the default browser behavior.", + required: false, + value: "boolean", + defaultValue: "true", + }, + { + name: "children", + description: "Drawer.Trigger and Drawer.Content components.", + required: true, + value: "ReactNode", + }, + ]} +/> + +### `Trigger` + +An optional button that opens the drawer. + + + +### `Close` + +An optional button that closes the drawer. + + + +### `Content` + +Contains [`Drawer.Header`](#header), [`Drawer.Body`](#body) and [`Drawer.Footer`](#footer) components to be rendered in the open drawer. + + + +### `Header` + +An optional container for the [`Drawer.Tagline`](#tagline), [`Drawer.Title`](#title) and [`Drawer.Description`](#description) components. + + + +### `Tagline` + +An optional tagline above the title. + + + +### `Title` + +An optional accessible title to be announced when the drawer is opened. + + + +### `Description` + +An optional accessible description to be announced when the drawer is opened. + + + +### `Body` + +A scrollable wrapper for all content that doesn’t belong to the [`Drawer.Header`](#header) or [`Drawer.Footer`](#footer). + + + +### `Footer` + +An optional footer wrapper. diff --git a/packages/ui/src/components/drawer.tsx b/packages/ui/src/components/drawer.tsx deleted file mode 100644 index dc238b094b..0000000000 --- a/packages/ui/src/components/drawer.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { - ComponentProps, - ComponentPropsWithoutRef, - ElementRef, - forwardRef, - HTMLAttributes, - PropsWithChildren, - useEffect, - useRef, - useState -} from 'react' - -import { ScrollArea } from '@/components' -import { usePortal } from '@/context' -import { cn } from '@utils/cn' -import { Drawer as DrawerPrimitive } from 'vaul' - -const DrawerRoot = ({ - shouldScaleBackground = true, - nested = false, - ...props -}: ComponentProps) => - nested ? ( - - ) : ( - - ) -DrawerRoot.displayName = 'DrawerRoot' - -const LazyDrawer = ({ - children, - open, - onOpenChange, - unmountOnClose = false, - ...props -}: ComponentProps & { unmountOnClose?: boolean }) => { - const [hasRendered, setHasRendered] = useState(open || false) - - const prevOpenState = useRef(false) - - useEffect(() => { - if (!prevOpenState.current && open) { - setHasRendered(true) - prevOpenState.current = true - } - // if unmountOnClose=true set hasRendered with delay - else if (unmountOnClose && prevOpenState.current && !open) { - const timer = setTimeout(() => { - setHasRendered(false) - prevOpenState.current = false - }, 250) - - return () => clearTimeout(timer) - } - }, [open]) - - return ( - - {hasRendered ? children : null} - - ) -} - -const DrawerTrigger = DrawerPrimitive.Trigger - -const DrawerPortal = DrawerPrimitive.Portal - -const DrawerClose = ({ - srOnly = false, - className, - ...props -}: ComponentProps & { - srOnly?: boolean -}) => { - return -} - -const DrawerOverlay = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName - -const DrawerContent = forwardRef< - ElementRef, - ComponentPropsWithoutRef & { nested?: boolean } ->(({ className, children, nested = false, ...props }, ref) => { - const { portalContainer } = usePortal() - - return ( - - - - {children} - - - - ) -}) -DrawerContent.displayName = 'DrawerContent' - -const DrawerInner = ({ - children, - className, - viewportClassName -}: PropsWithChildren>) => { - return ( - - {children} - - ) -} -DrawerInner.displayName = 'DrawerInner' - -const DrawerHeader = ({ className, ...props }: HTMLAttributes) => ( -
-) -DrawerHeader.displayName = 'DrawerHeader' - -const DrawerFooter = ({ className, ...props }: HTMLAttributes) => ( -
-) -DrawerFooter.displayName = 'DrawerFooter' - -const DrawerTitle = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DrawerTitle.displayName = DrawerPrimitive.Title.displayName - -const DrawerDescription = forwardRef< - ElementRef, - ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DrawerDescription.displayName = DrawerPrimitive.Description.displayName - -const Drawer = { - Root: DrawerRoot, - Lazy: LazyDrawer, - Portal: DrawerPortal, - Overlay: DrawerOverlay, - Trigger: DrawerTrigger, - Close: DrawerClose, - Content: DrawerContent, - Header: DrawerHeader, - Footer: DrawerFooter, - Title: DrawerTitle, - Description: DrawerDescription, - Inner: DrawerInner -} - -export { Drawer } diff --git a/packages/ui/src/components/drawer/Drawer.Tagline.tsx b/packages/ui/src/components/drawer/Drawer.Tagline.tsx new file mode 100644 index 0000000000..64b7cf372e --- /dev/null +++ b/packages/ui/src/components/drawer/Drawer.Tagline.tsx @@ -0,0 +1,8 @@ +import { HTMLAttributes } from 'react' + +import { cn } from '@/utils' + +export const DrawerTagline = ({ className, ...props }: HTMLAttributes) => ( + +) +DrawerTagline.displayName = 'DrawerTagline' diff --git a/packages/ui/src/components/drawer/DrawerBody.tsx b/packages/ui/src/components/drawer/DrawerBody.tsx new file mode 100644 index 0000000000..0ee5224cd4 --- /dev/null +++ b/packages/ui/src/components/drawer/DrawerBody.tsx @@ -0,0 +1,11 @@ +import { PropsWithChildren } from 'react' + +import { ScrollArea } from '@/components' +import { cn } from '@/utils' + +export const DrawerBody = ({ children, className }: PropsWithChildren<{ className?: string }>) => ( + + {children} + +) +DrawerBody.displayName = 'DrawerBody' diff --git a/packages/ui/src/components/drawer/DrawerContent.tsx b/packages/ui/src/components/drawer/DrawerContent.tsx new file mode 100644 index 0000000000..f0511d717b --- /dev/null +++ b/packages/ui/src/components/drawer/DrawerContent.tsx @@ -0,0 +1,73 @@ +import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react' + +import { Button, Icon } from '@/components' +import { usePortal } from '@/context' +import { cn } from '@/utils' +import { cva, VariantProps } from 'class-variance-authority' +import { Drawer as DrawerPrimitive } from 'vaul' + +import { useDrawerContext } from './drawer-context' +import { DrawerOverlay } from './DrawerOverlay' + +const drawerContentVariants = cva('cn-drawer-content', { + variants: { + size: { + sm: 'cn-drawer-content-sm', + md: 'cn-drawer-content-md', + lg: 'cn-drawer-content-lg' + }, + direction: { + right: 'cn-drawer-content-right', + left: 'cn-drawer-content-left', + top: 'cn-drawer-content-top', + bottom: 'cn-drawer-content-bottom' + } + }, + defaultVariants: { + size: 'sm', + direction: 'right' + } +}) + +export type DrawerContentVariantsSize = VariantProps['size'] +export type DrawerContentVariantsDirection = VariantProps['direction'] + +export type DrawerContentProps = ComponentPropsWithoutRef & { + size?: DrawerContentVariantsSize + hideClose?: boolean +} + +export const DrawerContent = forwardRef, DrawerContentProps>( + ({ className, children, size = 'sm', hideClose = false, ...props }, ref) => { + const { portalContainer } = usePortal() + const { direction } = useDrawerContext() + + return ( + + + + {!hideClose && ( + + + + )} + {children} + + + + ) + } +) +DrawerContent.displayName = 'DrawerContent' diff --git a/packages/ui/src/components/drawer/DrawerDescription.tsx b/packages/ui/src/components/drawer/DrawerDescription.tsx new file mode 100644 index 0000000000..c3f99b437e --- /dev/null +++ b/packages/ui/src/components/drawer/DrawerDescription.tsx @@ -0,0 +1,12 @@ +import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react' + +import { cn } from '@/utils' +import { Drawer as DrawerPrimitive } from 'vaul' + +export const DrawerDescription = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerDescription.displayName = DrawerPrimitive.Description.displayName diff --git a/packages/ui/src/components/drawer/DrawerFooter.tsx b/packages/ui/src/components/drawer/DrawerFooter.tsx new file mode 100644 index 0000000000..f06e8a7ad7 --- /dev/null +++ b/packages/ui/src/components/drawer/DrawerFooter.tsx @@ -0,0 +1,8 @@ +import { HTMLAttributes } from 'react' + +import { cn } from '@/utils' + +export const DrawerFooter = ({ className, ...props }: HTMLAttributes) => ( +
+) +DrawerFooter.displayName = 'DrawerFooter' diff --git a/packages/ui/src/components/drawer/DrawerHeader.tsx b/packages/ui/src/components/drawer/DrawerHeader.tsx new file mode 100644 index 0000000000..873e4104de --- /dev/null +++ b/packages/ui/src/components/drawer/DrawerHeader.tsx @@ -0,0 +1,66 @@ +import { Children, HTMLAttributes, ReactNode } from 'react' + +import { Icon, IconProps, Logo, LogoProps } from '@/components' +import { cn, getComponentDisplayName } from '@/utils' +import { Drawer as DrawerPrimitive } from 'vaul' + +import { DrawerTagline } from './Drawer.Tagline' + +type DrawerHeaderBaseProps = Omit, 'children'> & { + children: ReactNode +} + +type DrawerHeaderIconOnlyProps = { + icon: IconProps['name'] + logo?: never +} + +type DrawerHeaderLogoOnlyProps = { + logo: LogoProps['name'] + icon?: never +} + +type DrawerHeaderNoIconOrLogoProps = { + icon?: never + logo?: never +} + +export type DrawerHeaderProps = DrawerHeaderBaseProps & + (DrawerHeaderIconOnlyProps | DrawerHeaderLogoOnlyProps | DrawerHeaderNoIconOrLogoProps) + +export const DrawerHeader = ({ className, children, icon, logo, ...props }: DrawerHeaderProps) => { + const IconOrLogoComp = + (!!icon && ) || + (!!logo && ) || + null + + const { titleChildren, otherChildren } = Children.toArray(children).reduce<{ + titleChildren: ReactNode[] + otherChildren: ReactNode[] + }>( + (acc, child) => { + const displayName = getComponentDisplayName(child) + + if (displayName === DrawerPrimitive.Title.displayName || displayName === DrawerTagline.displayName) { + acc.titleChildren.push(child) + } else { + acc.otherChildren.push(child) + } + return acc + }, + { titleChildren: [], otherChildren: [] } + ) + + return ( +
+ {(!!titleChildren.length || !!IconOrLogoComp) && ( +
+ {IconOrLogoComp} +
{titleChildren}
+
+ )} + {otherChildren} +
+ ) +} +DrawerHeader.displayName = 'DrawerHeader' diff --git a/packages/ui/src/components/drawer/DrawerOverlay.tsx b/packages/ui/src/components/drawer/DrawerOverlay.tsx new file mode 100644 index 0000000000..950f8cae05 --- /dev/null +++ b/packages/ui/src/components/drawer/DrawerOverlay.tsx @@ -0,0 +1,22 @@ +import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react' + +import { cn } from '@/utils' +import { Drawer as DrawerPrimitive } from 'vaul' + +import { useDrawerContext } from './drawer-context' + +export const DrawerOverlay = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { nested } = useDrawerContext() + + return ( + + ) +}) +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName diff --git a/packages/ui/src/components/drawer/DrawerRoot.tsx b/packages/ui/src/components/drawer/DrawerRoot.tsx new file mode 100644 index 0000000000..7400a66108 --- /dev/null +++ b/packages/ui/src/components/drawer/DrawerRoot.tsx @@ -0,0 +1,53 @@ +import { ComponentProps, useEffect, useRef } from 'react' + +import { Drawer as DrawerPrimitive } from 'vaul' + +import { DrawerContext } from './drawer-context' + +export const DrawerRoot = ({ + nested = false, + direction = 'right', + open, + children, + onOpenChange, + ...props +}: ComponentProps) => { + const triggerRef = useRef(null) + + useEffect(() => { + if (!nested) return + + if (open && triggerRef.current) { + triggerRef.current.click() + } + + if (!open && !!onOpenChange) { + onOpenChange(false) + } + }, [nested, open, onOpenChange]) + + const FakeTrigger = ( + + - )} - + + {isCreate && !!onBack && ( + + )} + + )} diff --git a/packages/ui/src/views/connectors/connectors-pallete-drawer.tsx b/packages/ui/src/views/connectors/connectors-pallete-drawer.tsx index b62d5da43b..d95bf8cd5e 100644 --- a/packages/ui/src/views/connectors/connectors-pallete-drawer.tsx +++ b/packages/ui/src/views/connectors/connectors-pallete-drawer.tsx @@ -1,6 +1,6 @@ -import { ElementType, useMemo, useState } from 'react' +import { ElementType, ReactNode, useMemo, useState } from 'react' -import { Button, Drawer, EntityFormLayout, Input } from '@/components' +import { Button, ButtonGroup, Drawer, EntityFormLayout, Input } from '@/components' import { TranslationStore } from '@/views' import { ConnectorsPaletteSection } from './components/ConnectorsPalleteSection' @@ -12,20 +12,20 @@ const componentsMap: Record< Header: ElementType Title: ElementType Content: ElementType - Inner: ElementType + Body: ElementType } > = { true: { Header: Drawer.Header, Title: Drawer.Title, Content: Drawer.Content, - Inner: Drawer.Inner + Body: Drawer.Body }, false: { Header: EntityFormLayout.Header, Title: EntityFormLayout.Title, Content: 'div', - Inner: 'div' + Body: 'div' } } @@ -38,6 +38,7 @@ interface ConnectorsPaletteProps { title?: string isDrawer?: boolean showCategory?: boolean + children?: ReactNode } export const ConnectorsPalette = ({ @@ -48,10 +49,11 @@ export const ConnectorsPalette = ({ useTranslationStore, title = 'Connector Setup', isDrawer = false, - showCategory + showCategory, + children }: ConnectorsPaletteProps): JSX.Element => { const { t: _t } = useTranslationStore() - const { Header, Title, Content, Inner } = componentsMap[isDrawer ? 'true' : 'false'] + const { Header, Title, Content, Body } = componentsMap[isDrawer ? 'true' : 'false'] const [query, setQuery] = useState('') @@ -72,7 +74,7 @@ export const ConnectorsPalette = ({ }} /> - + { @@ -86,14 +88,17 @@ export const ConnectorsPalette = ({ useTranslationStore={useTranslationStore} showCategory={showCategory} /> - + {isDrawer && ( - + + + )} + {children} ) } diff --git a/packages/ui/src/views/delegates/delegate-selector/delegate-selector-form.tsx b/packages/ui/src/views/delegates/delegate-selector/delegate-selector-form.tsx index d66227d932..833f08dba4 100644 --- a/packages/ui/src/views/delegates/delegate-selector/delegate-selector-form.tsx +++ b/packages/ui/src/views/delegates/delegate-selector/delegate-selector-form.tsx @@ -23,15 +23,15 @@ const componentsMap: Record< 'true' | 'false', { Footer: ElementType - Inner: ElementType + Body: ElementType } > = { true: { - Inner: Drawer.Inner, + Body: Drawer.Body, Footer: Drawer.Footer }, false: { - Inner: 'div', + Body: 'div', Footer: EntityFormLayout.Footer } } @@ -96,7 +96,7 @@ export const DelegateSelectorForm: FC = ({ const [searchTag, setSearchTag] = useState('') const [matchedDelegates, setMatchedDelegates] = useState(0) - const { Inner, Footer } = componentsMap[isDrawer ? 'true' : 'false'] + const { Body, Footer } = componentsMap[isDrawer ? 'true' : 'false'] const formMethods = useForm({ resolver: zodResolver(delegateSelectorFormSchema), @@ -155,7 +155,7 @@ export const DelegateSelectorForm: FC = ({ return ( <> - +
{t('views:delegates.noDelegatesInstalled', `Haven't installed a delegate yet?`)} @@ -213,7 +213,7 @@ export const DelegateSelectorForm: FC = ({ )} - +
)} {!!error?.message &&

{error.message}

} - + - - + + + + ) diff --git a/packages/ui/src/views/unified-pipeline-studio/components/entity-form/unified-pipeline-studio-entity-form.tsx b/packages/ui/src/views/unified-pipeline-studio/components/entity-form/unified-pipeline-studio-entity-form.tsx index 5fdf73abe5..62d2a36443 100644 --- a/packages/ui/src/views/unified-pipeline-studio/components/entity-form/unified-pipeline-studio-entity-form.tsx +++ b/packages/ui/src/views/unified-pipeline-studio/components/entity-form/unified-pipeline-studio-entity-form.tsx @@ -1,6 +1,6 @@ import { ElementType, useEffect, useState } from 'react' -import { Button, Drawer, EntityFormLayout, Icon, SkeletonList } from '@/components' +import { Button, ButtonGroup, Drawer, EntityFormLayout, Icon, SkeletonList } from '@/components' import { useUnifiedPipelineStudioContext } from '@views/unified-pipeline-studio/context/unified-pipeline-studio-context' import { addNameInput } from '@views/unified-pipeline-studio/utils/entity-form-utils' import { get, isEmpty, isUndefined, omit, omitBy } from 'lodash-es' @@ -25,7 +25,7 @@ const componentsMap: Record< Header: ElementType Title: ElementType Description: ElementType - Inner: ElementType + Body: ElementType Footer: ElementType } > = { @@ -33,14 +33,14 @@ const componentsMap: Record< Header: Drawer.Header, Title: Drawer.Title, Description: Drawer.Description, - Inner: Drawer.Inner, + Body: Drawer.Body, Footer: Drawer.Footer }, false: { Header: EntityFormLayout.Header, Title: EntityFormLayout.Title, Description: EntityFormLayout.Description, - Inner: 'div', + Body: 'div', Footer: EntityFormLayout.Footer } } @@ -52,7 +52,7 @@ interface UnifiedPipelineStudioEntityFormProps { export const UnifiedPipelineStudioEntityForm = (props: UnifiedPipelineStudioEntityFormProps) => { const { requestClose, isDrawer = false } = props - const { Header, Title, Description, Inner, Footer } = componentsMap[isDrawer ? 'true' : 'false'] + const { Header, Title, Description, Body, Footer } = componentsMap[isDrawer ? 'true' : 'false'] const { yamlRevision, addStepIntention, @@ -230,7 +230,7 @@ export const UnifiedPipelineStudioEntityForm = (props: UnifiedPipelineStudioEnti {formEntity?.data.description} {/**/} - + {/* */} {/* General */} {/* Read documentation to learn more. */} @@ -244,28 +244,31 @@ export const UnifiedPipelineStudioEntityForm = (props: UnifiedPipelineStudioEnti )} - +
-
- - -
- {editStepIntention && ( - - )} + +
+ + +
+ {!!editStepIntention && ( + + )} +
)} diff --git a/packages/ui/src/views/unified-pipeline-studio/components/palette-drawer/uinfied-pipeline-step-palette-drawer.tsx b/packages/ui/src/views/unified-pipeline-studio/components/palette-drawer/uinfied-pipeline-step-palette-drawer.tsx index 80f6e9074d..ab751f6678 100644 --- a/packages/ui/src/views/unified-pipeline-studio/components/palette-drawer/uinfied-pipeline-step-palette-drawer.tsx +++ b/packages/ui/src/views/unified-pipeline-studio/components/palette-drawer/uinfied-pipeline-step-palette-drawer.tsx @@ -1,6 +1,6 @@ import { ElementType, useCallback, useMemo, useRef, useState } from 'react' -import { Button, Drawer, EntityFormLayout, Input, Pagination, Spacer } from '@/components' +import { Button, ButtonGroup, Drawer, EntityFormLayout, Input, Pagination, Spacer } from '@/components' import { useUnifiedPipelineStudioContext } from '@views/unified-pipeline-studio/context/unified-pipeline-studio-context' import { RightDrawer } from '@views/unified-pipeline-studio/types/right-drawer-types' @@ -12,20 +12,20 @@ const componentsMap: Record< { Header: ElementType Title: ElementType - Inner: ElementType + Body: ElementType Footer: ElementType } > = { true: { Header: Drawer.Header, Title: Drawer.Title, - Inner: Drawer.Inner, + Body: Drawer.Body, Footer: Drawer.Footer }, false: { Header: EntityFormLayout.Header, Title: EntityFormLayout.Title, - Inner: 'div', + Body: 'div', Footer: EntityFormLayout.Footer } } @@ -37,7 +37,7 @@ interface PipelineStudioStepFormProps { export const UnifiedPipelineStudioStepPalette = (props: PipelineStudioStepFormProps): JSX.Element => { const { requestClose, isDrawer = false } = props - const { Header, Title, Inner, Footer } = componentsMap[isDrawer ? 'true' : 'false'] + const { Header, Title, Body, Footer } = componentsMap[isDrawer ? 'true' : 'false'] const { setFormEntity, setRightDrawer, useTemplateListStore, useTranslationStore } = useUnifiedPipelineStudioContext() const { xNextPage, xPrevPage, setPage, templates, templatesError } = useTemplateListStore() @@ -78,7 +78,7 @@ export const UnifiedPipelineStudioStepPalette = (props: PipelineStudioStepFormPr }} /> - + - +
- + + +
) diff --git a/packages/ui/src/views/unified-pipeline-studio/components/stage-config/unified-pipeline-studio-stage-config-form.tsx b/packages/ui/src/views/unified-pipeline-studio/components/stage-config/unified-pipeline-studio-stage-config-form.tsx index b30ee1cb4e..161d33bdc4 100644 --- a/packages/ui/src/views/unified-pipeline-studio/components/stage-config/unified-pipeline-studio-stage-config-form.tsx +++ b/packages/ui/src/views/unified-pipeline-studio/components/stage-config/unified-pipeline-studio-stage-config-form.tsx @@ -23,7 +23,7 @@ const componentsMap: Record< Header: ElementType Title: ElementType Description: ElementType - Inner: ElementType + Body: ElementType Footer: ElementType } > = { @@ -32,7 +32,7 @@ const componentsMap: Record< Header: Drawer.Header, Title: Drawer.Title, Description: Drawer.Description, - Inner: Drawer.Inner, + Body: Drawer.Body, Footer: Drawer.Footer }, false: { @@ -40,7 +40,7 @@ const componentsMap: Record< Header: EntityFormLayout.Header, Title: EntityFormLayout.Title, Description: EntityFormLayout.Description, - Inner: Fragment, + Body: Fragment, Footer: EntityFormLayout.Footer } } @@ -52,7 +52,7 @@ interface UnifiedPipelineStudioStageConfigFormProps { export const UnifiedPipelineStudioStageConfigForm = (props: UnifiedPipelineStudioStageConfigFormProps) => { const { requestClose, isDrawer = false } = props - const { Content, Header, Title, Description, Inner, Footer } = componentsMap[isDrawer ? 'true' : 'false'] + const { Content, Header, Title, Description, Body, Footer } = componentsMap[isDrawer ? 'true' : 'false'] const { addStageIntention, @@ -119,30 +119,33 @@ export const UnifiedPipelineStudioStageConfigForm = (props: UnifiedPipelineStudi - + - +
- - - + + + + + + {!!editStageIntention && ( + + )} - {editStageIntention && ( - - )}
)} diff --git a/packages/ui/src/views/unified-pipeline-studio/components/unified-pipeline-stage-config-drawer.tsx b/packages/ui/src/views/unified-pipeline-studio/components/unified-pipeline-stage-config-drawer.tsx index bc77492db5..36107ac1fd 100644 --- a/packages/ui/src/views/unified-pipeline-studio/components/unified-pipeline-stage-config-drawer.tsx +++ b/packages/ui/src/views/unified-pipeline-studio/components/unified-pipeline-stage-config-drawer.tsx @@ -9,7 +9,6 @@ export const UnifiedPipelineStageConfigDrawer = () => { return ( { if (!open) { @@ -18,7 +17,7 @@ export const UnifiedPipelineStageConfigDrawer = () => { } }} > - + { setRightDrawer(RightDrawer.None) diff --git a/packages/ui/src/views/unified-pipeline-studio/components/unified-pipeline-step-drawer.tsx b/packages/ui/src/views/unified-pipeline-studio/components/unified-pipeline-step-drawer.tsx index 998690a2b3..e876fb4d9e 100644 --- a/packages/ui/src/views/unified-pipeline-studio/components/unified-pipeline-step-drawer.tsx +++ b/packages/ui/src/views/unified-pipeline-studio/components/unified-pipeline-step-drawer.tsx @@ -11,7 +11,6 @@ export const UnifiedPipelineStepDrawer = () => { return ( <> { if (!open) { @@ -20,7 +19,7 @@ export const UnifiedPipelineStepDrawer = () => { } }} > - + { setRightDrawer(RightDrawer.None) @@ -32,8 +31,6 @@ export const UnifiedPipelineStepDrawer = () => { {/* TODO: temporary outside to bypass shadow dom issue */} { if (!open) { @@ -42,7 +39,7 @@ export const UnifiedPipelineStepDrawer = () => { } }} > - + { setRightDrawer(RightDrawer.None) diff --git a/packages/ui/tailwind-design-system.ts b/packages/ui/tailwind-design-system.ts index afad7cd855..bb3ecb2bb5 100644 --- a/packages/ui/tailwind-design-system.ts +++ b/packages/ui/tailwind-design-system.ts @@ -23,7 +23,8 @@ import { radioStyles, switchStyles, tagStyles, - textareaStyles + textareaStyles, + drawerStyles } from './tailwind-utils-config/components' import { typography as typographyStyles } from './tailwind-utils-config/utilities' @@ -450,6 +451,7 @@ export default { cardStyles, cardSelectStyles, paginationStyles, + drawerStyles, // Form styles formSharedStyles, diff --git a/packages/ui/tailwind-utils-config/components/drawer.ts b/packages/ui/tailwind-utils-config/components/drawer.ts new file mode 100644 index 0000000000..3e57c01141 --- /dev/null +++ b/packages/ui/tailwind-utils-config/components/drawer.ts @@ -0,0 +1,140 @@ +export default { + '.cn-drawer': { + '&-content': { + backgroundColor: 'var(--cn-bg-2)', + borderColor: 'var(--cn-border-3)', + borderRadius: 'var(--cn-drawer-radius)', + boxShadow: 'var(--cn-shadow-5)', + '@apply fixed flex flex-col z-50 border': '', + + '&:where(.cn-drawer-content-right), &:where(.cn-drawer-content-left)': { + '@apply inset-y-0': '', + + '&:where(.cn-drawer-content-sm)': { + width: 'var(--cn-drawer-sm)' + }, + '&:where(.cn-drawer-content-md)': { + width: 'var(--cn-drawer-md)' + }, + '&:where(.cn-drawer-content-lg)': { + width: 'var(--cn-drawer-lg)' + } + }, + + '&:where(.cn-drawer-content-top), &:where(.cn-drawer-content-bottom)': { + '@apply inset-x-0': '', + + '&:where(.cn-drawer-content-sm)': { + height: 'var(--cn-drawer-sm)' + }, + '&:where(.cn-drawer-content-md)': { + height: 'var(--cn-drawer-md)' + }, + '&:where(.cn-drawer-content-lg)': { + height: 'var(--cn-drawer-lg)' + } + }, + + '&:where(.cn-drawer-content-right)' : { + '@apply border-r-0 rounded-r-none right-0': '' + }, + + '&:where(.cn-drawer-content-left)' : { + '@apply border-l-0 rounded-l-none left-0': '' + }, + + '&:where(.cn-drawer-content-top)' : { + '@apply border-t-0 rounded-t-none top-0': '' + }, + + '&:where(.cn-drawer-content-bottom)' : { + '@apply border-b-0 rounded-b-none bottom-0': '' + } + }, + + '&-close-button': { + '@apply absolute right-2 top-4 z-50': '', + + '&-icon': { + flexShrink: '0', + width: `var(--cn-icon-size-default)`, + height: `var(--cn-icon-size-default)` + } + }, + + '&-backdrop': { + backgroundColor: 'var(--cn-comp-dialog-backdrop)', + '@apply fixed inset-0 z-50': '', + + '&-nested': { + backgroundColor: 'var(--cn-comp-dialog-backdrop-nested)', + } + }, + + '&-header': { + borderBottomWidth: 'var(--cn-border-width-1)', + borderBottomColor: 'var(--cn-border-3)', + gap: 'var(--cn-drawer-gap)', + padding: 'var(--cn-drawer-container)', + '@apply flex flex-col border-b': '', + + '&-icon': { + flexShrink: '0', + width: 'var(--cn-icon-size-lg)', + height: 'var(--cn-icon-size-lg)' + }, + + '&-icon-color': { + color: 'var(--cn-text-2)', + }, + + '&-top': { + gap: 'var(--cn-spacing-2-half)', + paddingRight: 'var(--cn-spacing-6)', + '@apply flex items-center': '' + }, + + '&-title': { + '@apply flex flex-col': '' + } + }, + + '&-title': { + font: 'var(--cn-comp-dialog-title)', + color: 'var(--cn-text-1)' + }, + + '&-tagline': { + font: 'var(--cn-caption-soft)', + color: 'var(--cn-text-2)' + }, + + '&-description': { + font: 'var(--cn-body-normal)', + color: 'var(--cn-text-2)' + }, + + '&-body': { + padding: 'var(--cn-drawer-container)', + '@apply before:absolute before:inset-x-0 before:top-0 before:z-10 after:z-10 after:absolute after:inset-x-0 after:bottom-0': '', + + '&:before': { + height: 'var(--cn-drawer-fade-height)', + background: 'var(--cn-comp-dialog-fade-start)', + }, + + '&:after': { + height: 'var(--cn-drawer-fade-height)', + background: 'var(--cn-comp-dialog-fade-end)', + } + }, + + '&-footer': { + borderTopWidth: 'var(--cn-border-width-1)', + borderTopColor: 'var(--cn-border-3)', + gap: 'var(--cn-drawer-gap)', + padding: 'var(--cn-drawer-container)', + '@apply flex flex-col border-t': '' + } + } +} diff --git a/packages/ui/tailwind-utils-config/components/index.ts b/packages/ui/tailwind-utils-config/components/index.ts index bb9093470a..bd8390bb09 100644 --- a/packages/ui/tailwind-utils-config/components/index.ts +++ b/packages/ui/tailwind-utils-config/components/index.ts @@ -19,3 +19,4 @@ export { default as multiSelectV2Styles } from './multi-select-v2' export { default as cardSelectStyles } from './card-select' export { default as paginationStyles } from './pagination' export { default as layoutStyles } from './layout' +export { default as drawerStyles } from './drawer'