Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): Fix styling, centralize routing utils #5861

Merged
merged 11 commits into from
Jun 28, 2024
4 changes: 2 additions & 2 deletions apps/web/src/components/docs/DocsButton.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Popover } from '@mantine/core';
import { ActionButton, Button, IconOutlineMenuBook, QuickGuide, Tooltip } from '@novu/design-system';
import { useSegment } from '../providers/SegmentProvider';
import { useEffect, useMemo, useState } from 'react';
import { ComponentProps, useEffect, useMemo, useState } from 'react';
import { matchPath, useLocation } from 'react-router-dom';
import { css } from '@novu/novui/css';
import { Flex, styled } from '@novu/novui/jsx';
Expand Down Expand Up @@ -50,7 +50,7 @@ export const DocsButton = ({
tooltip,
}: {
TriggerButton?: React.FC<{ onClick: () => void }>;
tooltip?: string;
tooltip?: ComponentProps<typeof Tooltip>['label'];
}) => {
const [opened, setOpened] = useState<boolean>(false);
const segment = useSegment();
Expand Down
63 changes: 0 additions & 63 deletions apps/web/src/components/layout/components/LocalStudioHeader.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Text } from '@novu/novui';
import { IconOutlineArrowBack } from '@novu/novui/icons';
import { hstack } from '@novu/novui/patterns';

type BackButtonProps = { onClick: () => void };

export function BackButton({ onClick }: BackButtonProps) {
return (
<button
className={hstack({
cursor: 'pointer',
gap: 'margins.icons.Icon20-txt',
px: '75',
py: '25',
borderRadius: '75',
textStyle: 'text.secondary !important',
_hover: { bg: 'badge.border', '& p, & svg': { color: 'typography.text.main !important' } },
})}
Comment on lines +9 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I'm wondering, since we keep repeating this pattern of a lot of "inline" styling, ‏if we should maybe start a pattern of extracting this css classnames, just so we keep up readability

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely a good call-out! I need to extra this one specifically as a variant on the button, but a lot of these things are coming up because they're not really specced out in the design system, which would help us standardize them

onClick={onClick}
>
<IconOutlineArrowBack />
<Text variant="secondary" fontWeight={'strong'}>
Back
</Text>
</button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Header } from '@mantine/core';
import { IconButton } from '@novu/novui';
import { css } from '@novu/novui/css';
import { IconHelpOutline } from '@novu/novui/icons';
import { HStack } from '@novu/novui/jsx';
import { FC } from 'react';
import { discordInviteUrl } from '../../../../pages/quick-start/consts';
import { useStudioWorkflowsNavigation } from '../../../../studio/hooks';
import { DocsButton } from '../../../docs/DocsButton';
import { HEADER_NAV_HEIGHT } from '../../constants';
import { BridgeMenuItems } from '../v2/BridgeMenuItems';
import { BackButton } from './BackButton';

export const LocalStudioHeader: FC = () => {
const { goBack, shouldHideBackButton } = useStudioWorkflowsNavigation();

return (
<Header
height={`${HEADER_NAV_HEIGHT}px`}
className={css({
position: 'sticky',
top: 0,
borderBottom: 'none !important',
zIndex: 'sticky',
padding: '50',
})}
>
<HStack justifyContent="space-between" width="full" display="flex">
<HStack gap="100">{!shouldHideBackButton && <BackButton onClick={goBack} />}</HStack>
<HStack gap="100">
<BridgeMenuItems />
<DocsButton />
{/** TODO: this currently fails with Blocked opening in a new window
* because the request was made in a sandboxed frame whose 'allow-popups' permission is not set. */}
<IconButton Icon={IconHelpOutline} as="a" href={discordInviteUrl} target="_blank" rel="noopener noreferrer" />
</HStack>
</HStack>
</Header>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './LocalStudioHeader';
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as Sentry from '@sentry/react';
import { Outlet } from 'react-router-dom';
import { Outlet, useLocation } from 'react-router-dom';
import styled from '@emotion/styled';
import { css } from '@novu/novui/css';
import { LocalStudioHeader } from './LocalStudioHeader';
import { LocalStudioHeader } from './LocalStudioHeader/LocalStudioHeader';
import { LocalStudioSidebar } from './LocalStudioSidebar';
import { isStudioOnboardingRoute } from '../../../studio/utils/routing';

const AppShell = styled.div`
display: flex;
Expand All @@ -20,6 +21,8 @@ const ContentShell = styled.div`
`;

export function LocalStudioPageLayout() {
const { pathname } = useLocation();

return (
<Sentry.ErrorBoundary
fallback={({ error, eventId }) => (
Expand All @@ -40,7 +43,7 @@ export function LocalStudioPageLayout() {
<AppShell className={css({ '& *': { colorPalette: 'mode.local' } })}>
<LocalStudioSidebar />
<ContentShell>
<LocalStudioHeader />
{!isStudioOnboardingRoute(pathname) && <LocalStudioHeader />}
<Outlet />
</ContentShell>
</AppShell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import { LocalStudioSidebarOrganizationDisplay } from './LocalStudioSidebarOrgan
import { LocalStudioSidebarToggleButton } from './LocalStudioSidebarToggleButtonProps';
import { token } from '@novu/novui/tokens';
import { DocsButton } from '../../../docs/DocsButton';
import { hstack } from '@novu/novui/patterns';
import { css, cx } from '@novu/novui/css';
import { rawButtonBaseStyles } from '../../../nav/NavMenuButton/NavMenuButton.shared';
import { NavMenuButtonInner, rawButtonBaseStyles } from '../../../nav/NavMenuButton/NavMenuButton.shared';
import { useStudioState } from '../../../../studio/StudioStateProvider';

type LocalStudioSidebarContentProps = {
Expand All @@ -30,20 +29,16 @@ export const LocalStudioSidebarContent: FC<LocalStudioSidebarContentProps> = ({
<NavMenu variant="root">
<LocalStudioSidebarOrganizationDisplay title={organizationName || 'Your organization '} subtitle="Local studio" />
<NavMenuSection>
{/** TODO: handle click - link to doc page */}
<DocsButton
tooltip={'Open a guide'}
TriggerButton={({ onClick }) => (
<button
onClick={onClick}
className={cx(
hstack({ cursor: 'pointer' }),
css({ justifyContent: 'flex-start' }),
css(rawButtonBaseStyles)
)}
>
<IconAdd />
<span>Add a workflow</span>
<button onClick={onClick} className={css({ width: 'full' })}>
<NavMenuButtonInner
icon={<IconAdd />}
className={cx(css({ cursor: 'pointer', justifyContent: 'flex-start' }), css(rawButtonBaseStyles))}
>
Add a workflow
</NavMenuButtonInner>
</button>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ export const LocalStudioSidebarOrganizationDisplay: FC<LocalStudioSidebarOrganiz
className={css({
w: '125',
h: '125',

// TODO: use design system values when available
borderRadius: '8px',
borderRadius: '100',
})}
/>
<Stack gap="25">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,45 @@
import { css } from '@novu/novui/css';
import { IconBolt } from '@novu/novui/icons';
import { FC } from 'react';
import { WORKFLOW_NODE_STEP_ICON_DICTIONARY } from '../../../../studio/components/workflows/node-view/WorkflowNodes';
import { IBridgeWorkflow, IBridgeWorkflowStep } from '../../../../studio/types';
import { NavMenuToggleButton, NavMenuLinkButton } from '../../../nav/NavMenuButton';
import { ROUTES } from '../../../../constants/routes';
import { parseUrl } from '../../../../utils/routeUtils';
import { IBridgeWorkflow } from '../../../../studio/types';
import {
getStudioWorkflowLink,
getStudioWorkflowStepLink,
getStudioWorkflowTestLink,
} from '../../../../studio/utils/routing';
import { NavMenuLinkButton, NavMenuToggleButton } from '../../../nav/NavMenuButton';

type LocalStudioSidebarToggleButtonProps = {
workflow: IBridgeWorkflow;
};

const linkButtonClassName = css({ padding: '75', _before: { display: 'none' } });

export const LocalStudioSidebarToggleButton: FC<LocalStudioSidebarToggleButtonProps> = ({ workflow }) => {
const { workflowId, steps } = workflow;

return (
<NavMenuToggleButton label={workflowId} icon={null} link={getLinkFromWorkflow(workflowId)}>
<NavMenuLinkButton label={'Trigger'} link={getTriggerLink(workflowId)} icon={<IconBolt size="20" />} />
{steps.map((step) => {
const { stepId, type } = step;
<NavMenuToggleButton label={workflowId} icon={null} link={getStudioWorkflowLink(workflowId)}>
<NavMenuLinkButton
label={'Trigger'}
link={getStudioWorkflowTestLink(workflowId)}
icon={<IconBolt size="20" />}
className={linkButtonClassName}
/>
{steps.map(({ stepId, type }) => {
const Icon = WORKFLOW_NODE_STEP_ICON_DICTIONARY[type];

return (
<NavMenuLinkButton
key={`${workflowId}-${stepId}`}
label={stepId}
link={getLinkFromWorkflowStep(workflowId, step)}
link={getStudioWorkflowStepLink(workflowId, stepId)}
icon={<Icon size="20" title="studio-workflow-step-icon" />}
className={linkButtonClassName}
/>
);
})}
</NavMenuToggleButton>
);
};

function getTriggerLink(workflowId: string) {
return parseUrl(ROUTES.STUDIO_FLOWS_TEST, { templateId: workflowId });
}
function getLinkFromWorkflow(workflowId: string) {
return parseUrl(ROUTES.STUDIO_FLOWS_VIEW, { templateId: workflowId });
}

function getLinkFromWorkflowStep(workflowId: string, step: IBridgeWorkflowStep) {
return parseUrl(ROUTES.STUDIO_FLOWS_STEP_EDITOR, {
templateId: workflowId,
stepId: step.stepId,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { HeaderNav } from './v2/HeaderNav';
import { FreeTrialBanner } from './FreeTrialBanner';
import { css } from '@novu/novui/css';
import { EnvironmentEnum } from '../../../studio/constants/EnvironmentEnum';
import { isStudioRoute } from '../../../studio/utils/isStudioRoute';
import { isStudioRoute } from '../../../studio/utils/routing';

const AppShell = styled.div`
display: flex;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { LocalizedMessage } from '../../../types/LocalizedMessage';
import { ReactNode } from 'react';
import { css } from '@novu/novui/css';
import { CoreProps } from '@novu/novui';
import { CoreProps, CorePropsWithChildren } from '@novu/novui';
import { css, cx } from '@novu/novui/css';
import { IIconProps } from '@novu/novui/icons';
import { HStack } from '@novu/novui/jsx';
import { FC, ReactNode } from 'react';
import { truncatedFlexTextCss } from '../../../studio/utils/shared.styles';
import { LocalizedMessage } from '../../../types/LocalizedMessage';

export type RightSideTrigger = 'hover';

Expand Down Expand Up @@ -37,6 +39,10 @@ export const rawButtonBaseStyles = css.raw({
fontFamily: 'system',
cursor: 'pointer',

'& > *': {
...truncatedFlexTextCss,
},

// &.active is necessary to work with the react-router-dom className they generate
'& _active, &.active': {
position: 'relative',
Expand All @@ -58,3 +64,16 @@ export const rawButtonBaseStyles = css.raw({
boxShadow: 'medium',
},
});

type NavMenuButtonInnerProps = {
icon: ReactNode;
} & CorePropsWithChildren;

export const NavMenuButtonInner: FC<NavMenuButtonInnerProps> = ({ icon, children, className }) => {
return (
<HStack gap="75" className={cx(css(truncatedFlexTextCss), className)}>
{icon}
<p>{children}</p>
</HStack>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FC, PropsWithChildren, useState } from 'react';
import { NavLink } from 'react-router-dom';
import { css, cx } from '@novu/novui/css';
import { HStack } from '@novu/novui/jsx';
import { INavMenuButtonProps, rawButtonBaseStyles } from './NavMenuButton.shared';
import { INavMenuButtonProps, NavMenuButtonInner, rawButtonBaseStyles } from './NavMenuButton.shared';
import { NavMenuRightSide } from './NavMenuButtonRightSide';

const rawLinkButtonStyles = css.raw({
Expand Down Expand Up @@ -53,10 +53,7 @@ export const NavMenuLinkButton: FC<PropsWithChildren<INavMenuLinkButtonProps>> =
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<HStack gap="75">
{icon}
<span>{label}</span>
</HStack>
<NavMenuButtonInner icon={icon}>{label}</NavMenuButtonInner>
<NavMenuRightSide tooltip={rightSide?.tooltip} isMounted={shouldShowRightSide}>
{rightSide?.node}
</NavMenuRightSide>
Expand Down
Loading
Loading