diff --git a/apps/api/src/app/organization/dtos/update-branding-details.dto.ts b/apps/api/src/app/organization/dtos/update-branding-details.dto.ts index c70dccf7b41..7dabb11ccbc 100644 --- a/apps/api/src/app/organization/dtos/update-branding-details.dto.ts +++ b/apps/api/src/app/organization/dtos/update-branding-details.dto.ts @@ -1,8 +1,9 @@ import { IsHexColor, IsOptional, IsString, IsUrl } from 'class-validator'; import { IsImageUrl } from '../../shared/validators/image.validator'; -const protocols = process.env.NODE_ENV === 'production' ? ['https'] : ['http', 'https']; -const hostWhitelist = ['production', 'test'].includes(process.env.NODE_ENV) ? undefined : ['localhost', 'web.novu.co']; +const environments = ['production', 'test']; +const protocols = environments.includes(process.env.NODE_ENV) ? ['https'] : ['http', 'https']; +const hostWhitelist = environments.includes(process.env.NODE_ENV) ? undefined : ['localhost', 'web.novu.co']; export class UpdateBrandingDetailsDto { @IsUrl({ diff --git a/apps/web/cypress/tests/layout/main-nav.spec.ts b/apps/web/cypress/tests/layout/main-nav.spec.ts index 69d0e1a4f75..15d73a6a192 100644 --- a/apps/web/cypress/tests/layout/main-nav.spec.ts +++ b/apps/web/cypress/tests/layout/main-nav.spec.ts @@ -85,33 +85,27 @@ describe('Main Nav (Sidebar)', () => { cy.getByTestId('side-nav-settings-team-link').should('exist'); cy.getByTestId('side-nav-settings-branding-link').should('exist'); cy.getByTestId('side-nav-settings-billing-link').should('exist'); - - // TODO: remove these lines when we implement toggles for different envs cy.getByTestId('side-nav-settings-api-keys').should('exist'); cy.getByTestId('side-nav-settings-inbound-webhook').should('exist'); - - // TODO: re-enable once we implement the toggles for different envs + /** TODO: we will reinstate the toggle buttons w/ different envs once we have APIs to support the pages */ // cy.getByTestId('side-nav-settings-toggle-development').should('exist'); - // cy.getByTestId('side-nav-settings-toggle-production').should('exist'); }); - /** - * TODO: re-enable once we implement the toggles for different envs - * it('should display correct environment settings when toggling development/production', () => { - * // Navigate to the settings page - * cy.getByTestId('side-nav-settings-link').click(); - * - * // Toggle development environment - * cy.getByTestId('side-nav-settings-toggle-development').click(); - * cy.getByTestId('side-nav-settings-api-keys-development').should('exist'); - * cy.getByTestId('side-nav-settings-inbound-webhook-development').should('exist'); - * - * // Toggle production environment - * cy.getByTestId('side-nav-settings-toggle-production').click(); - * cy.getByTestId('side-nav-settings-api-keys-production').should('exist'); - * cy.getByTestId('side-nav-settings-inbound-webhook-production').should('exist'); - * }); - */ + /** TODO: we will reinstate the toggle buttons w/ different envs once we have APIs to support the pages */ + it.skip('should display correct environment settings when toggling development/production', () => { + // Navigate to the settings page + cy.getByTestId('side-nav-settings-link').click(); + + // Toggle development environment + cy.getByTestId('side-nav-settings-toggle-development').click(); + cy.getByTestId('side-nav-settings-api-keys-development').should('exist'); + cy.getByTestId('side-nav-settings-inbound-webhook-development').should('exist'); + + // Toggle production environment + cy.getByTestId('side-nav-settings-toggle-production').click(); + cy.getByTestId('side-nav-settings-api-keys-production').should('exist'); + cy.getByTestId('side-nav-settings-inbound-webhook-production').should('exist'); + }); }); }); diff --git a/apps/web/cypress/tests/notification-editor/create-notification.spec.ts b/apps/web/cypress/tests/notification-editor/create-notification.spec.ts index 5e728d99ca5..17f26c098c6 100644 --- a/apps/web/cypress/tests/notification-editor/create-notification.spec.ts +++ b/apps/web/cypress/tests/notification-editor/create-notification.spec.ts @@ -73,7 +73,7 @@ describe('Creation functionality', function () { cy.getByTestId('email-editor').getByTestId('editor-row').eq(1).click(); cy.getByTestId('control-add').click(); - cy.getByTestId('add-text-block').click(); + cy.getByTestId('add-text-block').click({ force: true }); cy.getByTestId('editable-text-content').eq(1).clear().type('This another text will be {{customVariable}}', { parseSpecialCharSequences: false, }); @@ -250,7 +250,7 @@ describe('Creation functionality', function () { cy.getByTestId('email-editor').getByTestId('editor-row').eq(1).click(); cy.getByTestId('control-add').click(); - cy.getByTestId('add-text-block').click(); + cy.getByTestId('add-text-block').click({ force: true }); cy.getByTestId('editable-text-content').eq(1).clear().type('This another text will be {{customVariable}}', { parseSpecialCharSequences: false, }); @@ -336,7 +336,7 @@ describe('Creation functionality', function () { cy.getByTestId('email-editor').getByTestId('editor-row').eq(1).click(); cy.getByTestId('control-add').click(); - cy.getByTestId('add-text-block').click(); + cy.getByTestId('add-text-block').click({ force: true }); cy.getByTestId('editable-text-content').eq(1).clear().type('This another text will be {{customVariable}}', { parseSpecialCharSequences: false, }); diff --git a/apps/web/cypress/tests/notification-editor/debugging-test-trigger.spec.ts b/apps/web/cypress/tests/notification-editor/debugging-test-trigger.spec.ts index df50630a3eb..33d5fadbfaf 100644 --- a/apps/web/cypress/tests/notification-editor/debugging-test-trigger.spec.ts +++ b/apps/web/cypress/tests/notification-editor/debugging-test-trigger.spec.ts @@ -7,7 +7,7 @@ describe('Debugging - test trigger', function () { const template = this.session.templates[0]; const userId = this.session.user.id; - cy.intercept('GET', 'http://127.0.0.1:1336/v1/notification-templates/*').as('notification-templates'); + cy.intercept('GET', '/v1/notification-templates/*').as('notification-templates'); cy.waitLoadTemplatePage(() => { cy.visit('/workflows/edit/' + template._id); diff --git a/apps/web/cypress/tests/organization-brand.spec.ts b/apps/web/cypress/tests/organization-brand.spec.ts index a8ad400c67b..0310821e6fa 100644 --- a/apps/web/cypress/tests/organization-brand.spec.ts +++ b/apps/web/cypress/tests/organization-brand.spec.ts @@ -1,5 +1,6 @@ describe('Organization Brand Screen', function () { beforeEach(function () { + cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: true }); cy.initializeSession().as('session'); cy.visit('settings/brand'); cy.intercept('*/organizations/branding').as('updateBrandingSettings'); @@ -26,6 +27,9 @@ describe('Organization Brand Screen', function () { cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', '.png'); cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', this.session.organization._id); + cy.getByTestId('font-color-picker').click({ force: true }); + cy.get('button[aria-label="#BA68C8"]').click(); + cy.get('body').click(); cy.getByTestId('submit-branding-settings').click(); cy.getByTestId('logo-image-wrapper').should('have.attr', 'src').should('include', '.png'); diff --git a/apps/web/cypress/tests/organization-page.spec.ts b/apps/web/cypress/tests/organization-page.spec.ts index c7bbefc05dc..9e20795c092 100644 --- a/apps/web/cypress/tests/organization-page.spec.ts +++ b/apps/web/cypress/tests/organization-page.spec.ts @@ -1,5 +1,6 @@ describe('Organization Page', function () { beforeEach(function () { + cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: true }); cy.initializeSession().as('session'); cy.visit('/settings/organization'); cy.intercept('*/organizations').as('renameOrganization'); diff --git a/apps/web/cypress/tests/organization-switch.spec.ts b/apps/web/cypress/tests/organization-switch.spec.ts index 789a2b79632..edbcbb4b7ea 100644 --- a/apps/web/cypress/tests/organization-switch.spec.ts +++ b/apps/web/cypress/tests/organization-switch.spec.ts @@ -19,7 +19,7 @@ describe('Organization Switch', function () { cy.task('addOrganization', this.session.user.id).then((newOrg: any) => { cy.visit('/workflows'); - cy.getByTestId('organization-switch').focus(); + cy.getByTestId('organization-switch').scrollIntoView().focus(); cy.get('.mantine-Select-item').contains(capitalize(newOrg.name)).click({ force: true }); diff --git a/apps/web/cypress/tests/settings.spec.ts b/apps/web/cypress/tests/settings.spec.ts index e210b9208bb..99e7639f188 100644 --- a/apps/web/cypress/tests/settings.spec.ts +++ b/apps/web/cypress/tests/settings.spec.ts @@ -1,5 +1,6 @@ describe('Settings Screen', function () { beforeEach(function () { + cy.mockFeatureFlags({ IS_INFORMATION_ARCHITECTURE_ENABLED: false }); cy.initializeSession().as('session'); cy.visit('/settings'); cy.intercept('*/channels/email/settings').as('updateEmailSettings'); diff --git a/apps/web/src/pages/settings/user-profile-page/UserProfilePasswordSidebar.tsx b/apps/web/src/pages/settings/user-profile-page/UserProfilePasswordSidebar.tsx index 9afa27e9e72..987c50b933e 100644 --- a/apps/web/src/pages/settings/user-profile-page/UserProfilePasswordSidebar.tsx +++ b/apps/web/src/pages/settings/user-profile-page/UserProfilePasswordSidebar.tsx @@ -1,4 +1,4 @@ -import { IconOutlineLockPerson, Sidebar } from '@novu/design-system'; +import { IconOutlineLockPerson, SidebarFormless } from '@novu/design-system'; import { useContext, useEffect, useMemo } from 'react'; import { HStack, styled } from '../../../styled-system/jsx'; import { title } from '../../../styled-system/recipes'; @@ -62,7 +62,7 @@ export const UserProfilePasswordSidebar: React.FC {sidebarContent} - + ); }; diff --git a/apps/web/src/pages/templates/components/StepEditorSidebar.tsx b/apps/web/src/pages/templates/components/StepEditorSidebar.tsx index a517aebad66..1b9bb05402d 100644 --- a/apps/web/src/pages/templates/components/StepEditorSidebar.tsx +++ b/apps/web/src/pages/templates/components/StepEditorSidebar.tsx @@ -1,14 +1,17 @@ +import { Sidebar } from '@novu/design-system'; +import { StepTypeEnum } from '@novu/shared'; import { ReactNode } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { StepTypeEnum } from '@novu/shared'; -import { Sidebar } from '@novu/design-system'; -import { useStepIndex } from '../hooks/useStepIndex'; -import { StepName } from './StepName'; +import { useFormContext } from 'react-hook-form'; import { useBasePath } from '../hooks/useBasePath'; -import { EditorSidebarHeaderActions } from './EditorSidebarHeaderActions'; -import { useStepVariantsCount } from '../hooks/useStepVariantsCount'; import { useNavigateToVariantPreview } from '../hooks/useNavigateToVariantPreview'; +import { useStepIndex } from '../hooks/useStepIndex'; +import { useStepVariantsCount } from '../hooks/useStepVariantsCount'; +import { EditorSidebarHeaderActions } from './EditorSidebarHeaderActions'; +import { IForm } from './formTypes'; +import { StepName } from './StepName'; +import { useTemplateEditorForm } from './TemplateEditorFormProvider'; const StepSidebarHeader = () => { const { channel } = useParams<{ @@ -35,6 +38,9 @@ export const StepEditorSidebar = ({ children }: { children: ReactNode }) => { const navigate = useNavigate(); const basePath = useBasePath(); const { navigateToVariantPreview } = useNavigateToVariantPreview(); + const { onSubmit, onInvalid } = useTemplateEditorForm(); + const methods = useFormContext(); + const { handleSubmit } = methods; const { stepIndex, variantIndex } = useStepIndex(); const { variantsCount } = useStepVariantsCount(); const key = `${stepIndex}_${variantIndex}`; @@ -46,6 +52,10 @@ export const StepEditorSidebar = ({ children }: { children: ReactNode }) => { StepTypeEnum.PUSH, ].includes(channel as StepTypeEnum); + const onSubmitHandler = async (data: IForm) => { + await onSubmit(data); + }; + return ( { navigate(basePath); }} data-test-id="step-editor-sidebar" + onSubmit={handleSubmit(onSubmitHandler, onInvalid)} > {children} diff --git a/apps/web/src/pages/templates/components/TestWorkflow.tsx b/apps/web/src/pages/templates/components/TestWorkflow.tsx index a2cb90905d1..380ef479643 100644 --- a/apps/web/src/pages/templates/components/TestWorkflow.tsx +++ b/apps/web/src/pages/templates/components/TestWorkflow.tsx @@ -25,7 +25,7 @@ const makeToValue = (subscriberVariables: INotificationTriggerVariable[], curren const subsVars = getSubscriberValue( subscriberVariables, (variable) => - (currentUser && currentUser[variable.name === 'subscriberId' ? 'id' : variable.name]) || '' + (currentUser && currentUser[variable.name === 'subscriberId' ? '_id' : variable.name]) || '' ); return JSON.stringify(subsVars, null, 2); diff --git a/apps/web/tests/start-from-scratch-tour.spec.ts b/apps/web/tests/start-from-scratch-tour.spec.ts index 9463676dadb..3fee6e5df7e 100644 --- a/apps/web/tests/start-from-scratch-tour.spec.ts +++ b/apps/web/tests/start-from-scratch-tour.spec.ts @@ -1,5 +1,4 @@ import { test, expect } from '@playwright/test'; -import os from 'node:os'; import { getByTestId, initializeSession } from './utils.ts/browser'; @@ -143,7 +142,7 @@ test('should show the dots navigation after the intro step', async ({ page }) => await expect(dotsNavigation).toBeVisible(); }); -test('should show not show the start from scratch tour hints after it is shown twice ', async ({ page }) => { +test.skip('should not show the start from scratch tour hints after it is shown twice ', async ({ page }) => { await page.goto('/workflows/create'); let scratchWorkflowTooltip = await getByTestId(page, 'scratch-workflow-tooltip'); diff --git a/libs/design-system/src/sidebar/Sidebar.const.ts b/libs/design-system/src/sidebar/Sidebar.const.ts new file mode 100644 index 00000000000..01b62885a1c --- /dev/null +++ b/libs/design-system/src/sidebar/Sidebar.const.ts @@ -0,0 +1,7 @@ +export const COLLAPSED_WIDTH = 600; +export const NAVIGATION_WIDTH = 272; +/** + * @deprecated + * TODO:Remove this once Information Architecture is 100% live + */ +export const LEGACY_NAVIGATION_WIDTH = 300; diff --git a/libs/design-system/src/sidebar/Sidebar.styles.ts b/libs/design-system/src/sidebar/Sidebar.styles.ts new file mode 100644 index 00000000000..436ba48e06e --- /dev/null +++ b/libs/design-system/src/sidebar/Sidebar.styles.ts @@ -0,0 +1,83 @@ +import { css } from '@emotion/css'; +import styled from '@emotion/styled'; +import { createStyles, MantineTheme } from '@mantine/core'; +import { FeatureFlagsKeysEnum } from '@novu/shared'; +import { useFeatureFlag } from '@novu/shared-web'; +import { colors, shadows } from '../config'; +import { NAVIGATION_WIDTH, LEGACY_NAVIGATION_WIDTH, COLLAPSED_WIDTH } from './Sidebar.const'; +import { ISidebarBaseProps } from './Sidebar.types'; + +export const HeaderHolder = styled.div` + display: flex; + flex-wrap: nowrap; + gap: 12px; + margin: 24px; + margin-bottom: 0; +`; + +export const scrollable = css` + overflow-x: hidden; + overflow-y: auto; +`; + +export const BodyHolder = styled.div<{ isParentScrollable: boolean }>` + display: flex; + flex-direction: column; + ${(props) => !props.isParentScrollable && scrollable}; + margin: 0 24px; + gap: 24px; + padding-right: 5px; + margin-right: 19px; + height: 100%; +`; + +export const FooterHolder = styled.div` + display: flex; + flex-wrap: nowrap; + gap: 6px; + margin: 24px; + margin-top: 0; + margin-top: auto; +`; + +export const useDrawerStyles = createStyles( + (theme: MantineTheme, { isExpanded }: Pick) => { + /** + * TODO: Remove this feature flag and navigationWidth once the information architecture is enabled for all users + */ + const isInformationArchitectureEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_INFORMATION_ARCHITECTURE_ENABLED); + const navigationWidth = isInformationArchitectureEnabled ? NAVIGATION_WIDTH : LEGACY_NAVIGATION_WIDTH; + + return { + root: { + position: 'absolute', + }, + drawer: { + position: 'fixed', + top: 40, + right: 0, + bottom: 0, + backgroundColor: theme.colorScheme === 'dark' ? colors.B17 : colors.white, + borderTopLeftRadius: 7, + borderBottomLeftRadius: 7, + boxShadow: shadows.dark, + width: isExpanded ? `calc(100% - ${navigationWidth}px)` : COLLAPSED_WIDTH, + transition: 'all 300ms ease !important', + '@media screen and (max-width: 768px)': { + width: isExpanded ? `100%` : COLLAPSED_WIDTH, + }, + }, + body: { + height: '100%', + }, + }; + } +); + +export const sidebarDrawerContentClassName = css` + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + gap: 24px; +`; diff --git a/libs/design-system/src/sidebar/Sidebar.tsx b/libs/design-system/src/sidebar/Sidebar.tsx index 49e5fe513ac..30348dcfb9c 100644 --- a/libs/design-system/src/sidebar/Sidebar.tsx +++ b/libs/design-system/src/sidebar/Sidebar.tsx @@ -1,81 +1,28 @@ import styled from '@emotion/styled'; -import { createStyles, CSSObject, Drawer, DrawerStylesNames, Loader, MantineTheme, Stack, Styles } from '@mantine/core'; -import { useFeatureFlag, useKeyDown } from '@novu/shared-web'; -import React, { ReactNode } from 'react'; -import { css, cx } from '@emotion/css'; -import { FeatureFlagsKeysEnum } from '@novu/shared'; +import { Drawer, Loader, Stack } from '@mantine/core'; +import { useKeyDown } from '@novu/shared-web'; + import { ActionButton } from '../button/ActionButton'; -import { colors, shadows } from '../config'; +import { colors } from '../config'; import { ArrowLeft } from '../icons'; import { When } from '../when'; import { Close } from './Close'; +import { BodyHolder, FooterHolder, HeaderHolder, scrollable, useDrawerStyles } from './Sidebar.styles'; +import { ISidebarBaseProps } from './Sidebar.types'; -const HeaderHolder = styled.div` - display: flex; - flex-wrap: nowrap; - gap: 12px; - margin: 24px; - margin-bottom: 0; -`; - -const scrollable = css` - overflow-x: hidden; - overflow-y: auto; -`; - -const BodyHolder = styled.div<{ isParentScrollable: boolean }>` - display: flex; - flex-direction: column; - ${(props) => !props.isParentScrollable && scrollable}; - margin: 0 24px; - gap: 24px; - padding-right: 5px; - margin-right: 19px; - height: 100%; -`; - -const FooterHolder = styled.div` - display: flex; - flex-wrap: nowrap; - gap: 6px; - margin: 24px; - margin-top: 0; - margin-top: auto; -`; - -const COLLAPSED_WIDTH = 600; -const NAVIGATION_WIDTH = 272; -const LEGACY_NAVIGATION_WIDTH = 300; - -const useDrawerStyles = createStyles((theme: MantineTheme) => { - return { - root: { - position: 'absolute', - }, - drawer: { - position: 'fixed', - top: 40, - right: 0, - bottom: 0, - backgroundColor: theme.colorScheme === 'dark' ? colors.B17 : colors.white, - borderTopLeftRadius: 7, - borderBottomLeftRadius: 7, - boxShadow: shadows.dark, - }, - body: { - height: '100%', - }, - }; -}); - -const sidebarDrawerContentClassName = css` +const Form = styled.form<{ isParentScrollable: boolean }>` height: 100%; overflow: hidden; display: flex; flex-direction: column; gap: 24px; + ${(props) => props.isParentScrollable && scrollable}; `; +export interface ISidebarProps extends ISidebarBaseProps { + onSubmit?: React.FormEventHandler; +} + export const Sidebar = ({ customFooter, customHeader, @@ -89,103 +36,19 @@ export const Sidebar = ({ onClose, onBack, onSubmit, -}: { - customHeader?: ReactNode; - customFooter?: ReactNode; - children: ReactNode; - isOpened: boolean; - isExpanded?: boolean; - isLoading?: boolean; - isParentScrollable?: boolean; - styles?: Styles>; - onClose: () => void; - onBack?: () => void; - onSubmit?: React.FormEventHandler; - 'data-test-id'?: string; -}) => { - const { classes: drawerClasses } = useDrawerStyles(); +}: ISidebarProps) => { + const { classes: drawerClasses } = useDrawerStyles({ isExpanded }); const onCloseCallback = () => { onClose(); }; - /** - * TODO: Remove this feature flag and navigationWidth once the information architecture is enabled for all users - */ - const isInformationArchitectureEnabled = useFeatureFlag(FeatureFlagsKeysEnum.IS_INFORMATION_ARCHITECTURE_ENABLED); - const navigationWidth = isInformationArchitectureEnabled ? NAVIGATION_WIDTH : LEGACY_NAVIGATION_WIDTH; - useKeyDown('Escape', onCloseCallback); - /** - * This is a temporary solution to a overloaded pattern. The Sidebar component should - * not have an embedded form as it removes the caller from properly controlling their own form. - * We will refactor the Sidebar later on as part of the design system work. - * - * https://linear.app/novu/issue/NV-3632/de-couple-the-sidebar-from-its-internal-form - */ - const wrappedContent = ( -
- - {isExpanded && onBack && ( - svg': { - width: 16, - height: 16, - }, - }} - /> - )} - {customHeader} - svg': { - width: 14, - height: 14, - }, - }} - data-test-id="sidebar-close" - /> - - - - - - - - {children} - - {customFooter && {customFooter}} -
- ); - return ( - {onSubmit ? ( -
{ - e.stopPropagation(); - }} - > - {wrappedContent} -
- ) : ( - wrappedContent - )} +
{ + e.stopPropagation(); + }} + > + + {isExpanded && onBack && ( + svg': { + width: 16, + height: 16, + }, + }} + /> + )} + {customHeader} + svg': { + width: 14, + height: 14, + }, + }} + data-test-id="sidebar-close" + /> + + + + + + + + {children} + + {customFooter && {customFooter}} +
); }; diff --git a/libs/design-system/src/sidebar/Sidebar.types.ts b/libs/design-system/src/sidebar/Sidebar.types.ts new file mode 100644 index 00000000000..b7a6b4110e1 --- /dev/null +++ b/libs/design-system/src/sidebar/Sidebar.types.ts @@ -0,0 +1,17 @@ +import { Styles, DrawerStylesNames } from '@mantine/core'; +import { ReactNode } from 'react'; + +export interface ISidebarBaseProps { + customHeader?: ReactNode; + customFooter?: ReactNode; + children: ReactNode; + isOpened: boolean; + isExpanded?: boolean; + isLoading?: boolean; + isParentScrollable?: boolean; + styles?: Styles>; + onClose: () => void; + onBack?: () => void; + onSubmit?: React.FormEventHandler; + 'data-test-id'?: string; +} diff --git a/libs/design-system/src/sidebar/SidebarFormless.tsx b/libs/design-system/src/sidebar/SidebarFormless.tsx new file mode 100644 index 00000000000..36558ba4872 --- /dev/null +++ b/libs/design-system/src/sidebar/SidebarFormless.tsx @@ -0,0 +1,113 @@ +import { cx } from '@emotion/css'; +import { Drawer, Loader, Stack } from '@mantine/core'; +import { useKeyDown } from '@novu/shared-web'; +import { ActionButton } from '../button/ActionButton'; +import { colors } from '../config'; +import { ArrowLeft } from '../icons'; +import { When } from '../when'; +import { Close } from './Close'; +import { + BodyHolder, + FooterHolder, + HeaderHolder, + scrollable, + sidebarDrawerContentClassName, + useDrawerStyles, +} from './Sidebar.styles'; +import { ISidebarBaseProps } from './Sidebar.types'; + +/** + * A Sidebar component without the form element that wraps content. + * + * This is a temporary solution to a overloaded pattern. The Sidebar component should + * not have an embedded form as it removes the caller from properly controlling their own form. + * We will refactor the Sidebar later on as part of the design system work. + * + * https://linear.app/novu/issue/NV-3632/de-couple-the-sidebar-from-its-internal-form + */ + +export const SidebarFormless = ({ + customFooter, + customHeader, + children, + isOpened, + isExpanded = false, + isLoading = false, + isParentScrollable = false, + styles, + 'data-test-id': dataTestId, + onClose, + onBack, +}: ISidebarBaseProps) => { + const { classes: drawerClasses } = useDrawerStyles({ isExpanded }); + const onCloseCallback = () => { + onClose(); + }; + + useKeyDown('Escape', onCloseCallback); + + return ( + +
+ + {isExpanded && onBack && ( + svg': { + width: 16, + height: 16, + }, + }} + /> + )} + {customHeader} + svg': { + width: 14, + height: 14, + }, + }} + data-test-id="sidebar-close" + /> + + + + + + + + {children} + + {customFooter && {customFooter}} +
+
+ ); +}; diff --git a/libs/design-system/src/sidebar/index.ts b/libs/design-system/src/sidebar/index.ts index c167c49f6f1..206a70e9da0 100644 --- a/libs/design-system/src/sidebar/index.ts +++ b/libs/design-system/src/sidebar/index.ts @@ -1 +1,3 @@ export * from './Sidebar'; +export * from './SidebarFormless'; +export * from './Sidebar.const';