From 0203e774dce98c6c3d2f3c273b8c1176282aa32d Mon Sep 17 00:00:00 2001 From: Joel Anton Date: Fri, 14 Jun 2024 07:26:20 -0700 Subject: [PATCH] feat(novui,web): Add IconButton, build aesthetics of flow editor (#5730) * feat: Add IconButton * feat: Workflow editor page --- .cspell.json | 1 + apps/web/panda.config.ts | 45 ++++++++- apps/web/src/AppRoutes.tsx | 2 +- .../studio/components/workflows/StepNode.tsx | 46 +++++++++ .../workflows/WorkflowFloatingMenu.tsx | 99 +++++++++++++++++++ .../workflows/WorkflowsDetailPage.tsx | 66 ++++++++++--- .../components/button/IconButton.stories.tsx | 36 +++++++ .../src/components/button/IconButton.tsx | 58 +++++++++++ libs/novui/src/components/button/index.ts | 1 + .../novui/src/tokens/semanticColors.tokens.ts | 6 ++ 10 files changed, 347 insertions(+), 13 deletions(-) create mode 100644 apps/web/src/studio/components/workflows/StepNode.tsx create mode 100644 apps/web/src/studio/components/workflows/WorkflowFloatingMenu.tsx create mode 100644 libs/novui/src/components/button/IconButton.stories.tsx create mode 100644 libs/novui/src/components/button/IconButton.tsx diff --git a/.cspell.json b/.cspell.json index 667a8bbb5da..16c402de548 100644 --- a/.cspell.json +++ b/.cspell.json @@ -634,6 +634,7 @@ "prepush", "xcodebuild", "liquidjs", + "vstack" "nimma", "jpath", "Nimma", diff --git a/apps/web/panda.config.ts b/apps/web/panda.config.ts index 753a4204076..d79b37ca47a 100644 --- a/apps/web/panda.config.ts +++ b/apps/web/panda.config.ts @@ -38,7 +38,50 @@ export default defineConfig({ * theme-oriented that is unique to `web`, include it below */ theme: { - extend: {}, + extend: { + semanticTokens: { + colors: { + workflow: { + node: { + surface: { + value: { base: '{colors.legacy.white}', _dark: '{colors.legacy.B17}' }, + type: 'color', + }, + connector: { + value: { base: '{colors.legacy.B40}', _dark: '{colors.legacy.B40}' }, + type: 'color', + }, + }, + background: { + dots: { + value: { base: '{colors.legacy.BGLight}', _dark: '{colors.legacy.B20}' }, + type: 'color', + }, + }, + }, + }, + spacing: { + workflow: { + nodes: { + gap: { + value: '{spacing.250}', + type: 'spacing', + }, + }, + }, + }, + sizes: { + workflow: { + nodes: { + gap: { + value: '{spacing.250}', + type: 'sizes', + }, + }, + }, + }, + }, + }, }, outExtension: 'js', diff --git a/apps/web/src/AppRoutes.tsx b/apps/web/src/AppRoutes.tsx index bfd13afbfba..fce51337f77 100644 --- a/apps/web/src/AppRoutes.tsx +++ b/apps/web/src/AppRoutes.tsx @@ -143,7 +143,7 @@ export const AppRoutes = () => { } /> } /> - } /> + } /> } /> diff --git a/apps/web/src/studio/components/workflows/StepNode.tsx b/apps/web/src/studio/components/workflows/StepNode.tsx new file mode 100644 index 00000000000..651158eb005 --- /dev/null +++ b/apps/web/src/studio/components/workflows/StepNode.tsx @@ -0,0 +1,46 @@ +import { Title } from '@novu/novui'; +import { css, cx } from '@novu/novui/css'; +import { hstack } from '@novu/novui/patterns'; +import { FC } from 'react'; +import { LocalizedMessage } from '../../../types/LocalizedMessage'; +interface IStepNodeProps { + icon: React.ReactNode; + title: LocalizedMessage; +} + +export const StepNode: FC = ({ icon, title }) => { + return ( + + ); +}; diff --git a/apps/web/src/studio/components/workflows/WorkflowFloatingMenu.tsx b/apps/web/src/studio/components/workflows/WorkflowFloatingMenu.tsx new file mode 100644 index 00000000000..2e5046f072d --- /dev/null +++ b/apps/web/src/studio/components/workflows/WorkflowFloatingMenu.tsx @@ -0,0 +1,99 @@ +import { Tooltip } from '@novu/design-system'; +import { IconButton, Text, type CoreProps, type IconButtonProps } from '@novu/novui'; +import { css, cx } from '@novu/novui/css'; +import { + IconOutlineAutoAwesomeMotion, + IconOutlineAvTimer, + IconOutlineEmail, + IconOutlineForum, + IconOutlineMobileFriendly, + IconOutlineNotifications, + IconOutlineSms, + IconType, +} from '@novu/novui/icons'; +import { VStack } from '@novu/novui/jsx'; +import { vstack } from '@novu/novui/patterns'; +import { FC, PropsWithChildren } from 'react'; +import { LocalizedMessage } from '../../../types/LocalizedMessage'; + +type IWorkflowFloatingMenuProps = CoreProps; + +export const WorkflowFloatingMenu: FC = ({ className }) => { + return ( + + + + + + + + + + + + + + ); +}; + +interface IWorkflowFloatingMenuSectionProps extends PropsWithChildren { + title: LocalizedMessage; +} + +function WorkflowFloatingMenuSection({ title, children }: IWorkflowFloatingMenuSectionProps) { + return ( + + + {title} + + {children} + + ); +} + +interface IWorkflowFloatingMenuButtonProps extends IconButtonProps { + Icon: IconType; + tooltipLabel?: LocalizedMessage; +} + +function WorkflowFloatingMenuButton({ Icon, tooltipLabel }: IWorkflowFloatingMenuButtonProps) { + return ( + + + + ); +} diff --git a/apps/web/src/studio/components/workflows/WorkflowsDetailPage.tsx b/apps/web/src/studio/components/workflows/WorkflowsDetailPage.tsx index 16217116f66..c91d774e563 100644 --- a/apps/web/src/studio/components/workflows/WorkflowsDetailPage.tsx +++ b/apps/web/src/studio/components/workflows/WorkflowsDetailPage.tsx @@ -1,23 +1,67 @@ -import { SearchInput } from '@novu/design-system'; -import { Button } from '@novu/novui'; +import { Button, IconButton, Title } from '@novu/novui'; import { css } from '@novu/novui/css'; -import { IconAddBox } from '@novu/novui/icons'; -import { Flex } from '@novu/novui/jsx'; +import { + IconCable, + IconOutlineBolt, + IconOutlineEmail, + IconOutlineNotifications, + IconPlayArrow, + IconSettings, +} from '@novu/novui/icons'; +import { Flex, HStack, VStack } from '@novu/novui/jsx'; import { PageContainer } from '../../layout/PageContainer'; import { PageMeta } from '../../layout/PageMeta'; +import { StepNode } from './StepNode'; +import { WorkflowFloatingMenu } from './WorkflowFloatingMenu'; export const WorkflowsDetailPage = () => { - const title = 'Example'; + const title = 'Workflow name'; + + const handleSettingsClick = () => {}; + const handleTestClick = () => {}; return ( - + - - - + + + + {title} + + + + + + + + + } title={'Workflow trigger'} /> + } title={'Workflow trigger'} /> + } title={'Workflow trigger'} /> + + ); }; diff --git a/libs/novui/src/components/button/IconButton.stories.tsx b/libs/novui/src/components/button/IconButton.stories.tsx new file mode 100644 index 00000000000..68036f3bc42 --- /dev/null +++ b/libs/novui/src/components/button/IconButton.stories.tsx @@ -0,0 +1,36 @@ +import { Meta } from '@storybook/react'; +import React from 'react'; +import { Grid } from '../../../styled-system/jsx'; +import { IconSettings } from '../../icons'; +import { Title } from '../title'; +import { IconButton } from './IconButton'; + +export default { + title: 'Components/IconButton', + component: IconButton, + argTypes: {}, +} as Meta; + +export const all = () => ( + + Default + + + + + Transparent + + + + + Filled + + + + + Outline + + + + +); diff --git a/libs/novui/src/components/button/IconButton.tsx b/libs/novui/src/components/button/IconButton.tsx new file mode 100644 index 00000000000..d3051e4421b --- /dev/null +++ b/libs/novui/src/components/button/IconButton.tsx @@ -0,0 +1,58 @@ +import { ActionIcon, ButtonVariant as ExternalButtonVariant } from '@mantine/core'; +import React from 'react'; +import { PolymorphicComponentPropWithRef, PolymorphicRef } from '../../types/props-helpers'; +import { JsxStyleProps } from 'styled-system/types'; +import { css, cx } from '../../../styled-system/css'; +import { button, type ButtonVariant } from '../../../styled-system/recipes'; +import { token } from '../../../styled-system/tokens'; +import { IconType } from '../../icons'; +import { CoreProps } from '../../types'; +import { splitCssProps } from '../../../styled-system/jsx'; + +interface IIconButtonProps { + Icon: IconType; + loading?: boolean; +} + +type IconButtonDefaultElement = 'button'; + +export type IconButtonProps = PolymorphicComponentPropWithRef< + C, + JsxStyleProps & ButtonVariant & CoreProps & IIconButtonProps +>; + +const DEFAULT_VARIANT: ButtonVariant['variant'] = 'transparent'; + +type PolymorphicComponent = ( + props: IconButtonProps +) => JSX.Element | null; + +/** + * A button with only an Icon. + * + * TODO: there are not specifications for these in the Design System, so this just follows the Button recipe. + */ +export const IconButton: PolymorphicComponent = React.forwardRef( + ( + { variant = DEFAULT_VARIANT, ...props }: IconButtonProps, + ref?: PolymorphicRef + ) => { + const [variantProps, buttonProps] = button.splitVariantProps({ ...props, variant }); + const [cssProps, localProps] = splitCssProps(buttonProps); + const { className, as, loading, Icon, ...otherProps } = localProps; + const styles = button(variantProps); + + return ( + + + + ); + } +); diff --git a/libs/novui/src/components/button/index.ts b/libs/novui/src/components/button/index.ts index 8b166a86e4d..66f0cf90a7d 100644 --- a/libs/novui/src/components/button/index.ts +++ b/libs/novui/src/components/button/index.ts @@ -1 +1,2 @@ export * from './Button'; +export * from './IconButton'; diff --git a/libs/novui/src/tokens/semanticColors.tokens.ts b/libs/novui/src/tokens/semanticColors.tokens.ts index 69c6c95a7f1..d8602b044d7 100644 --- a/libs/novui/src/tokens/semanticColors.tokens.ts +++ b/libs/novui/src/tokens/semanticColors.tokens.ts @@ -66,6 +66,12 @@ export const LEGACY_COLOR_SEMANTIC_TOKENS = defineSemanticTokens.colors({ type: 'color', }, }, + icon: { + filled: { + value: { base: '{colors.legacy.white}', _dark: '{colors.legacy.white}' }, + type: 'color', + }, + }, secondary: { background: { value: { base: '{colors.legacy.white}', _dark: '{colors.legacy.B17}' },