diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index bb432202046..ebef315a23e 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -6,6 +6,8 @@ import { initializeApp } from './initializeApp'; import reportWebVitals from './reportWebVitals'; import { LAUNCH_DARKLY_CLIENT_SIDE_ID } from '@novu/shared-web'; +// TODO: would like to figure out a better solution, but this unblocks for now +import '@novu/novui/components.css'; import '@novu/novui/styles.css'; (async () => { diff --git a/apps/web/src/studio/components/workflows/WorkflowsListPage.tsx b/apps/web/src/studio/components/workflows/WorkflowsListPage.tsx index ec3c94ce7dd..98fbf0aaf07 100644 --- a/apps/web/src/studio/components/workflows/WorkflowsListPage.tsx +++ b/apps/web/src/studio/components/workflows/WorkflowsListPage.tsx @@ -1,9 +1,11 @@ +import { Button } from '@novu/novui'; import { PageContainer } from '../../layout'; import { WorkflowsTable } from './table'; export const WorkflowsListPage = () => { return ( + ); diff --git a/libs/novui/.storybook/preview.tsx b/libs/novui/.storybook/preview.tsx index 75698172913..73fad068c87 100644 --- a/libs/novui/.storybook/preview.tsx +++ b/libs/novui/.storybook/preview.tsx @@ -5,9 +5,7 @@ import { css } from '../styled-system/css'; import { MantineThemeProvider } from '@mantine/core'; import { NovuiProvider } from '../src/components'; -import '@mantine/core/styles.css'; -// Bring in the Panda-generated stylesheets -import '../src/index.css'; +import '../styles.css'; export const parameters: Parameters = { layout: 'fullscreen', diff --git a/libs/novui/package.json b/libs/novui/package.json index 511662a3952..818aa93ae75 100644 --- a/libs/novui/package.json +++ b/libs/novui/package.json @@ -51,7 +51,8 @@ "require": "./styled-system/jsx/index.js", "import": "./styled-system/jsx/index.js" }, - "./styles.css": "./styled-system/styles.css" + "./styles.css": "./styled-system/styles.css", + "./components.css": "./node_modules/@mantine/core/styles.css" }, "scripts": { "prepare:lib": "pnpm prepare:panda && pnpm prepare:audit", diff --git a/libs/novui/src/components/NovuiProvider.tsx b/libs/novui/src/components/NovuiProvider.tsx index e15b0f4ef24..d85900ccd90 100644 --- a/libs/novui/src/components/NovuiProvider.tsx +++ b/libs/novui/src/components/NovuiProvider.tsx @@ -1,12 +1,14 @@ import { MantineProvider } from '@mantine/core'; import { FC, PropsWithChildren } from 'react'; import { IconProvider } from '../icons/IconProvider'; +import { MANTINE_THEME } from './mantine-theme.config'; + type INovuiProviderProps = PropsWithChildren; /** Used to export a v7 Mantine provider */ export const NovuiProvider: FC = ({ children }) => { return ( - + {children} ); diff --git a/libs/novui/src/components/button/Button.stories.tsx b/libs/novui/src/components/button/Button.stories.tsx new file mode 100644 index 00000000000..4ae5848d0e2 --- /dev/null +++ b/libs/novui/src/components/button/Button.stories.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { StoryFn, Meta } from '@storybook/react'; +import { Button } from './Button'; +import { Flex } from '../../../styled-system/jsx'; +import { Icon10K, IconInfo } from '../../icons'; + +export default { + title: 'Components/Button', + component: Button, + argTypes: {}, +} as Meta; + +const Template: StoryFn = ({ ...args }) => ; + +export const Default = Template.bind({}); +Default.args = {}; + +export const Loading = Template.bind({}); +Loading.args = { + loading: true, +}; + +export const icon = () => ( + + + + +); + +export const filled = () => ( + + + + +); + +export const outline = () => ( + + + + +); + +export const disabled = () => ( + + + + +); diff --git a/libs/novui/src/components/button/Button.tsx b/libs/novui/src/components/button/Button.tsx new file mode 100644 index 00000000000..d0c0e98c99d --- /dev/null +++ b/libs/novui/src/components/button/Button.tsx @@ -0,0 +1,25 @@ +import { FC } from 'react'; +import { CorePropsWithChildren } from '../../types'; +import { Button as ExternalButton, ButtonProps } from '@mantine/core'; +import { IconType } from '../../icons'; +import { css } from '../../../styled-system/css'; + +export interface IButtonProps + extends CorePropsWithChildren, + React.ButtonHTMLAttributes, + Pick { + Icon?: IconType; +} + +export const Button: FC = ({ children, Icon, size = 'md', variant = 'filled', ...buttonProps }) => { + return ( + : undefined} + variant={variant} + {...buttonProps} + > + {children} + + ); +}; diff --git a/libs/novui/src/components/button/index.ts b/libs/novui/src/components/button/index.ts new file mode 100644 index 00000000000..8b166a86e4d --- /dev/null +++ b/libs/novui/src/components/button/index.ts @@ -0,0 +1 @@ +export * from './Button'; diff --git a/libs/novui/src/components/index.ts b/libs/novui/src/components/index.ts index c67e1b4c37b..4456b720ed9 100644 --- a/libs/novui/src/components/index.ts +++ b/libs/novui/src/components/index.ts @@ -1,3 +1,4 @@ export * from './NovuiProvider'; export * from './table'; export * from './Test'; +export * from './button'; diff --git a/libs/novui/src/components/mantine-theme.config.ts b/libs/novui/src/components/mantine-theme.config.ts new file mode 100644 index 00000000000..850d11490d5 --- /dev/null +++ b/libs/novui/src/components/mantine-theme.config.ts @@ -0,0 +1,87 @@ +import { MantineColorsTuple, MantineThemeOverride } from '@mantine/core'; +import { COLOR_PALETTE_TOKENS } from '../tokens/colors.tokens'; +import { token, Token } from '../../styled-system/tokens'; + +/** + * Generates a Mantine color tuple for the given Panda color "family" + */ +const generateMantineColorTokens = (colorFamily: keyof typeof COLOR_PALETTE_TOKENS): MantineColorsTuple => { + return Object.keys(COLOR_PALETTE_TOKENS[colorFamily]).map((paletteNumber) => + token(`colors.${colorFamily}.${paletteNumber}.dark` as Token) + ) as unknown as MantineColorsTuple; +}; + +/** Maps Panda token values to a mantine theme config */ +export const MANTINE_THEME: MantineThemeOverride = { + // colors + white: token('colors.legacy.white'), + black: token('colors.legacy.black'), + primaryColor: 'gradient', + primaryShade: 6, + colors: { + gray: generateMantineColorTokens('mauve'), + yellow: generateMantineColorTokens('amber'), + blue: generateMantineColorTokens('blue'), + green: generateMantineColorTokens('green'), + red: generateMantineColorTokens('red'), + // must have a tuple of 10 strings, but replace the value at primaryShade with our gradient + gradient: ['', '', '', '', '', '', token('gradients.horizontal'), '', '', ''], + }, + + // typography + fontFamily: token('fonts.system'), + fontFamilyMonospace: token('fonts.mono'), + lineHeights: { + sm: token('lineHeights.100'), + md: token('lineHeights.125'), + lg: token('lineHeights.150'), + // missing 175 + xl: token('lineHeights.200'), + }, + headings: { + fontFamily: token('fonts.system'), + fontWeight: token('fontWeights.strong'), + sizes: { + // page title + h1: { + fontSize: token('fontSizes.150'), + lineHeight: token('lineHeights.200'), + }, + // section title + h2: { + fontSize: token('fontSizes.125'), + lineHeight: token('lineHeights.175'), + }, + // subsection title + h3: { + fontSize: token('fontSizes.100'), + lineHeight: token('lineHeights.150'), + }, + }, + }, + + // TODO: these are guesses for how they match up + spacing: { + xs: token('spacing.25'), + sm: token('spacing.50'), + md: token('spacing.100'), + lg: token('spacing.150'), + xl: token('spacing.200'), + xxl: token('spacing.250'), + xxxl: token('spacing.300'), + }, + radius: { + xs: token('radii.xs'), + sm: token('radii.s'), + md: token('radii.m'), + lg: token('radii.l'), + }, + defaultRadius: 'md', + shadows: { + // TODO: this makes no sense except for md + sm: token('shadows.light'), + md: token('shadows.medium'), + lg: token('shadows.dark'), + xl: token('shadows.color'), + }, +};