Setup the foundation for Twenty UI library. (#4423)

* feat: create a separate package for twenty-ui, extract the pill component with hard-coded theme values into it, and use the component inside twenty-front to complete the setup

* feat: extract the light and the dark theme into twenty-ui and update the AppThemeProvider component inside twenty-front to consume themes from twenty-ui

* fix: create a decorator inside preview.tsx to provide a default theme to storybook development server

* fix: remove redundant type declarations and revert back the naming convention for theme declarations

* fix: introduce a default value for pill label within the story for development server

* fix: introduce the nx script into package.json for twenty-ui and resolve imports for theme type within the package

* fix: remove the pill component from the twenty-front package along with the story for it

* fix: revert the package versions to those before running the nx cli command for storybook init

* feat: update readme to include details for building the ui library and starting the storybook development server

* fix: include details about twenty-ui inside jest.config for twenty-front to complete front-jest job

* - Added preview head for font
- Added theme addon for light/dark switch
- Added ComponentDecorator

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Abdullah 2024-03-13 18:21:18 +05:00 committed by GitHub
parent 4e1e4e2c4c
commit 8c0680b918
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 2601 additions and 146 deletions

4
.gitignore vendored
View File

@ -19,4 +19,6 @@
coverage
.vercel
**/**/logs/**
**/**/logs/**
storybook-static

47
nx.json
View File

@ -2,38 +2,26 @@
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"start": {
"cache": true,
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"lint": {
"cache": true
},
"test": {
"cache": true,
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"test:e2e": {
"cache": true,
"dependsOn": [
"^build"
]
"dependsOn": ["^build"]
},
"@nx/jest:jest": {
"cache": true,
"inputs": [
"default",
"^default",
"{workspaceRoot}/jest.preset.js"
],
"inputs": ["default", "^default", "{workspaceRoot}/jest.preset.js"],
"options": {
"passWithNoTests": true
},
@ -46,10 +34,7 @@
},
"@nx/vite:test": {
"cache": true,
"inputs": [
"default",
"^default"
]
"inputs": ["default", "^default"]
}
},
"installation": {
@ -67,5 +52,23 @@
"unitTestRunner": "none"
}
}
},
"plugins": [
{
"plugin": "@nx/storybook/plugin",
"options": {
"buildStorybookTargetName": "build-storybook",
"serveStorybookTargetName": "storybook",
"testStorybookTargetName": "test-storybook",
"staticStorybookTargetName": "static-storybook"
}
}
],
"tasksRunnerOptions": {
"default": {
"options": {
"cacheableOperations": ["build-storybook"]
}
}
}
}
}

View File

@ -51,6 +51,7 @@
"@sentry/tracing": "^7.99.0",
"@sniptt/guards": "^0.2.0",
"@stoplight/elements": "^8.0.5",
"@storybook/icons": "^1.2.9",
"@swc/jest": "^0.2.29",
"@tabler/icons-react": "^2.44.0",
"@types/facepaint": "^1.2.5",
@ -192,22 +193,28 @@
"@nx/jest": "17.2.8",
"@nx/js": "17.2.8",
"@nx/react": "17.2.8",
"@nx/storybook": "^17.2.8",
"@nx/vite": "17.2.8",
"@nx/web": "17.2.8",
"@storybook/addon-actions": "^7.6.3",
"@storybook/addon-coverage": "^1.0.0",
"@storybook/addon-essentials": "^7.6.7",
"@storybook/addon-interactions": "^7.6.7",
"@storybook/addon-links": "^7.6.7",
"@storybook/addon-onboarding": "^1.0.10",
"@storybook/addon-themes": "^7.6.7",
"@storybook/addon-themes": "^8.0.0",
"@storybook/blocks": "^7.6.3",
"@storybook/core-server": "7.6.3",
"@storybook/jest": "^0.2.3",
"@storybook/react": "^7.6.3",
"@storybook/react-vite": "^7.6.3",
"@storybook/test": "^7.6.3",
"@storybook/test-runner": "^0.16.0",
"@storybook/testing-library": "^0.2.2",
"@stylistic/eslint-plugin": "^1.5.0",
"@swc-node/register": "~1.6.7",
"@swc/core": "~1.3.100",
"@swc/helpers": "~0.5.2",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "14.0.0",
"@types/apollo-upload-client": "^17.0.2",
@ -245,6 +252,7 @@
"@typescript-eslint/parser": "^6.10.0",
"@typescript-eslint/utils": "^6.9.1",
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/ui": "~0.34.6",
"chromatic": "^6.18.0",
"concurrently": "^8.2.2",
"cross-var": "^1.1.0",
@ -268,6 +276,7 @@
"http-server": "^14.1.1",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"jest-environment-node": "^29.4.1",
"jest-fetch-mock": "^3.0.3",
"jsdom": "~22.1.0",
"msw": "^2.0.11",
@ -290,7 +299,8 @@
"vite": "^5.0.0",
"vite-plugin-checker": "^0.6.2",
"vite-plugin-dts": "~2.3.0",
"vite-plugin-svgr": "^4.2.0"
"vite-plugin-svgr": "^4.2.0",
"vitest": "~0.34.6"
},
"engines": {
"node": "^18.17.1",
@ -315,6 +325,7 @@
"packages/twenty-docs",
"packages/twenty-server",
"packages/twenty-emails",
"packages/twenty-ui",
"packages/twenty-utils",
"packages/twenty-zapier",
"packages/twenty-website",

View File

@ -9,6 +9,7 @@ export default {
moduleNameMapper: {
'~/(.+)': '<rootDir>/src/$1',
'@/(.+)': '<rootDir>/src/modules/$1',
'twenty-ui': '<rootDir>/../twenty-ui/src/index.ts',
'@testing/(.+)': '<rootDir>/src/testing/$1',
'\\.(jpg|jpeg|png|gif|webp|svg|svg\\?react)$':
'<rootDir>/__mocks__/imageMock.js',

View File

@ -1,10 +1,10 @@
import { ReactNode } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Pill } from 'twenty-ui';
import { IconChevronRight } from '@/ui/display/icon';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { Pill } from '@/ui/display/pill/components/Pill';
import { Card } from '@/ui/layout/card/components/Card';
import { CardContent } from '@/ui/layout/card/components/CardContent';

View File

@ -1,9 +1,9 @@
import React from 'react';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Pill } from 'twenty-ui';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { Pill } from '@/ui/display/pill/components/Pill';
export type ButtonSize = 'medium' | 'small';
export type ButtonPosition = 'standalone' | 'left' | 'middle' | 'right';

View File

@ -1,9 +1,9 @@
import * as React from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { Pill } from 'twenty-ui';
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { Pill } from '@/ui/display/pill/components/Pill';
type TabProps = {
id: string;

View File

@ -1,7 +1,5 @@
import { ThemeProvider } from '@emotion/react';
import { THEME_DARK } from '@/ui/theme/constants/ThemeDark';
import { THEME_LIGHT } from '@/ui/theme/constants/ThemeLight';
import { THEME_DARK, THEME_LIGHT } from 'twenty-ui';
import { useColorScheme } from '../hooks/useColorScheme';
import { useSystemColorScheme } from '../hooks/useSystemColorScheme';

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled';
import { Pill } from 'twenty-ui';
import { IconArrowUpRight, IconBolt } from '@/ui/display/icon';
import { Pill } from '@/ui/display/pill/components/Pill';
import { Button } from '@/ui/input/button/components/Button';
import { SettingsIntegration } from '~/pages/settings/integrations/types/SettingsIntegration';

View File

@ -10,7 +10,8 @@
"baseUrl": ".",
"paths": {
"@/*": ["src/modules/*"],
"~/*": ["src/*"]
"~/*": ["src/*"],
"twenty-ui": ["../twenty-ui/src/index.ts"]
},
/* Bundler mode */
@ -27,7 +28,7 @@
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
},
"files": [],
"include": [],

View File

@ -15,7 +15,6 @@ export * from './src/modules/ui/display/checkmark/components/AnimatedCheckmark'
export * from './src/modules/ui/display/chip/components/Chip'
export * from './src/modules/ui/display/chip/components/EntityChip'
export * from './src/modules/ui/display/icon/components/IconAddressBook'
export * from './src/modules/ui/display/pill/components/Pill'
export * from './src/modules/ui/display/tag/components/Tag'
export * from './src/modules/ui/display/tooltip/AppTooltip'
export * from './src/modules/ui/display/tooltip/OverflowingTextWithTooltip'

View File

@ -0,0 +1,25 @@
{
"extends": ["../../.eslintrc.js"],
"ignorePatterns": ["!**/*", "storybook-static"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}

1
packages/twenty-ui/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dist

View File

@ -0,0 +1,24 @@
import { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-themes',
],
framework: {
name: '@storybook/react-vite',
options: {
builder: {
viteConfigPath: 'vite.config.ts',
},
},
},
};
export default config;
// To customize your Vite configuration you can use the viteFinal field.
// Check https://storybook.js.org/docs/react/builders/vite#configuration
// and https://nx.dev/recipes/storybook/custom-builder-configs

View File

@ -0,0 +1,34 @@
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.7/iframeResizer.contentWindow.min.js"></script>
<style type="text/css">
body {
margin: 0;
font-family: 'Inter', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
html {
font-size: 13px;
}
.sbdocs-wrapper {
padding: 0 !important;
}
*::-webkit-scrollbar {
height: 4px;
width: 4px;
}
*::-webkit-scrollbar-corner {
background-color: transparent;
}
*::-webkit-scrollbar-thumb {
background-color: transparent;
border-radius: 2px;
}
</style>

View File

@ -0,0 +1,21 @@
import { ThemeProvider } from '@emotion/react';
import { withThemeFromJSXProvider } from '@storybook/addon-themes';
import { Preview, ReactRenderer } from '@storybook/react';
import { THEME_DARK, THEME_LIGHT } from '../src/theme/index';
const preview: Preview = {
// TODO: Add toggle for darkTheme.
decorators: [
withThemeFromJSXProvider<ReactRenderer>({
themes: {
light: THEME_LIGHT,
dark: THEME_DARK,
},
defaultTheme: 'light',
Provider: ThemeProvider,
}),
],
};
export default preview;

View File

@ -0,0 +1,15 @@
# Twenty UI
This library was generated with [Nx](https://nx.dev).
## Building
Run `yarn nx build twenty-ui` to build the library.
## Storybook Server
Run `yarn nx start twenty-ui` to start the storybook development server on `localhost:6006`.
## Running unit tests
Run `yarn nx test twenty-ui` to execute the unit tests via [Jest](https://jestjs.io).

View File

@ -0,0 +1,11 @@
/* eslint-disable */
export default {
displayName: 'twenty-ui',
preset: '../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/packages/twenty-ui',
};

View File

@ -0,0 +1,12 @@
{
"name": "twenty-ui",
"version": "0.0.1",
"main": "./index.js",
"module": "./index.mjs",
"typings": "./index.d.ts",
"scripts": {
"nx": "NX_DEFAULT_PROJECT=twenty-ui node ../../node_modules/nx/bin/nx.js",
"start": "storybook dev -p 6006",
"build": "storybook build"
}
}

View File

@ -0,0 +1,27 @@
{
"name": "twenty-ui",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/twenty-ui/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/vite:build",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "packages/twenty-ui/dist"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "packages/twenty-ui/jest.config.ts"
}
}
},
"tags": []
}

View File

@ -1,16 +1,20 @@
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { ComponentDecorator } from '../../testing/decorators/ComponentDecorator';
import { Pill } from '../Pill';
import { Pill } from './Pill';
const meta: Meta<typeof Pill> = {
title: 'UI/Display/Pill/Pill',
component: Pill,
decorators: [ComponentDecorator],
};
export default meta;
type Story = StoryObj<typeof Pill>;
export const Default: Story = {};
export const Default: Story = {
args: {
label: 'Soon',
},
decorators: [ComponentDecorator],
};

View File

@ -1,3 +1,4 @@
import * as React from 'react';
import styled from '@emotion/styled';
type PillProps = {
@ -21,6 +22,6 @@ const StyledPill = styled.span`
padding: ${({ theme }) => `0 ${theme.spacing(2)}`};
`;
export const Pill = ({ className, label }: PillProps) => (
<StyledPill className={className}>{label}</StyledPill>
);
export const Pill = ({ className, label }: PillProps) => {
return <StyledPill className={className}>{label}</StyledPill>;
};

View File

@ -0,0 +1 @@
export { Pill } from './Pill/Pill';

5
packages/twenty-ui/src/emotion.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { ThemeType } from './theme';
declare module '@emotion/react' {
export interface Theme extends ThemeType {}
}

View File

@ -0,0 +1,2 @@
export * from './components';
export * from './theme';

View File

@ -0,0 +1,28 @@
import styled from '@emotion/styled';
const StyledLayout = styled.div<{ width?: number }>`
background: ${({ theme }) => theme.background.primary};
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: 5px;
display: flex;
flex-direction: row;
height: fit-content;
max-width: calc(100% - 40px);
min-width: ${({ width }) => (width ? 'unset' : '300px')};
padding: 20px;
width: ${({ width }) => (width ? width + 'px' : 'fit-content')};
`;
type ComponentStorybookLayoutProps = {
width?: number;
children: JSX.Element;
};
export const ComponentStorybookLayout = ({
width,
children,
}: ComponentStorybookLayoutProps) => (
<StyledLayout width={width}>{children}</StyledLayout>
);

View File

@ -0,0 +1,13 @@
import { Decorator } from '@storybook/react';
import { ComponentStorybookLayout } from '../ComponentStorybookLayout';
export const ComponentDecorator: Decorator = (Story, context) => {
const { container } = context.parameters;
return (
<ComponentStorybookLayout width={container?.width}>
<Story />
</ComponentStorybookLayout>
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,10 @@
import { COLOR } from './Colors';
export const ACCENT_DARK = {
primary: COLOR.blueAccent75,
secondary: COLOR.blueAccent80,
tertiary: COLOR.blueAccent85,
quaternary: COLOR.blueAccent90,
accent3570: COLOR.blueAccent70,
accent4060: COLOR.blueAccent60,
};

View File

@ -0,0 +1,10 @@
import { COLOR } from './Colors';
export const ACCENT_LIGHT = {
primary: COLOR.blueAccent25,
secondary: COLOR.blueAccent20,
tertiary: COLOR.blueAccent15,
quaternary: COLOR.blueAccent10,
accent3570: COLOR.blueAccent35,
accent4060: COLOR.blueAccent40,
};

View File

@ -0,0 +1,9 @@
export const ANIMATION = {
duration: {
instant: 0.075,
fast: 0.15,
normal: 0.3,
},
};
export type AnimationDuration = 'instant' | 'fast' | 'normal';

View File

@ -0,0 +1,27 @@
/* eslint-disable @nx/workspace-no-hardcoded-colors */
import DarkNoise from '../assets/dark-noise.jpg';
import { COLOR } from './Colors';
import { GRAY_SCALE } from './GrayScale';
import { RGBA } from './Rgba';
export const BACKGROUND_DARK = {
noisy: `url(${DarkNoise.toString()});`,
primary: GRAY_SCALE.gray85,
secondary: GRAY_SCALE.gray80,
tertiary: GRAY_SCALE.gray75,
quaternary: GRAY_SCALE.gray70,
danger: COLOR.red80,
transparent: {
primary: RGBA(GRAY_SCALE.gray85, 0.8),
secondary: RGBA(GRAY_SCALE.gray80, 0.8),
strong: RGBA(GRAY_SCALE.gray0, 0.14),
medium: RGBA(GRAY_SCALE.gray0, 0.1),
light: RGBA(GRAY_SCALE.gray0, 0.06),
lighter: RGBA(GRAY_SCALE.gray0, 0.03),
danger: RGBA(COLOR.red, 0.08),
},
overlay: RGBA(GRAY_SCALE.gray80, 0.8),
radialGradient: `radial-gradient(50% 62.62% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
radialGradientHover: `radial-gradient(76.32% 95.59% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
};

View File

@ -0,0 +1,27 @@
/* eslint-disable @nx/workspace-no-hardcoded-colors */
import LightNoise from '../assets/light-noise.png';
import { COLOR } from './Colors';
import { GRAY_SCALE } from './GrayScale';
import { RGBA } from './Rgba';
export const BACKGROUND_LIGHT = {
noisy: `url(${LightNoise.toString()});`,
primary: GRAY_SCALE.gray0,
secondary: GRAY_SCALE.gray10,
tertiary: GRAY_SCALE.gray15,
quaternary: GRAY_SCALE.gray20,
danger: COLOR.red10,
transparent: {
primary: RGBA(GRAY_SCALE.gray0, 0.8),
secondary: RGBA(GRAY_SCALE.gray10, 0.8),
strong: RGBA(GRAY_SCALE.gray100, 0.16),
medium: RGBA(GRAY_SCALE.gray100, 0.08),
light: RGBA(GRAY_SCALE.gray100, 0.04),
lighter: RGBA(GRAY_SCALE.gray100, 0.02),
danger: RGBA(COLOR.red, 0.08),
},
overlay: RGBA(GRAY_SCALE.gray80, 0.8),
radialGradient: `radial-gradient(50% 62.62% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
radialGradientHover: `radial-gradient(76.32% 95.59% at 50% 0%, #505050 0%, ${GRAY_SCALE.gray60} 100%)`,
};

View File

@ -0,0 +1,4 @@
export const BLUR = {
light: 'blur(6px)',
strong: 'blur(20px)',
};

View File

@ -0,0 +1,10 @@
export const BORDER_COMMON = {
radius: {
xs: '2px',
sm: '4px',
md: '8px',
xl: '20px',
pill: '999px',
rounded: '100%',
},
};

View File

@ -0,0 +1,15 @@
import { BORDER_COMMON } from './BorderCommon';
import { COLOR } from './Colors';
import { GRAY_SCALE } from './GrayScale';
export const BORDER_DARK = {
color: {
strong: GRAY_SCALE.gray55,
medium: GRAY_SCALE.gray65,
light: GRAY_SCALE.gray70,
secondaryInverted: GRAY_SCALE.gray35,
inverted: GRAY_SCALE.gray20,
danger: COLOR.red70,
},
...BORDER_COMMON,
};

View File

@ -0,0 +1,15 @@
import { BORDER_COMMON } from './BorderCommon';
import { COLOR } from './Colors';
import { GRAY_SCALE } from './GrayScale';
export const BORDER_LIGHT = {
color: {
strong: GRAY_SCALE.gray25,
medium: GRAY_SCALE.gray20,
light: GRAY_SCALE.gray15,
secondaryInverted: GRAY_SCALE.gray50,
inverted: GRAY_SCALE.gray60,
danger: COLOR.red20,
},
...BORDER_COMMON,
};

View File

@ -0,0 +1,18 @@
import { GRAY_SCALE } from './GrayScale';
import { RGBA } from './Rgba';
export const BOX_SHADOW_DARK = {
light: `0px 2px 4px 0px ${RGBA(
GRAY_SCALE.gray100,
0.04,
)}, 0px 0px 4px 0px ${RGBA(GRAY_SCALE.gray100, 0.08)}`,
strong: `2px 4px 16px 0px ${RGBA(
GRAY_SCALE.gray100,
0.16,
)}, 0px 2px 4px 0px ${RGBA(GRAY_SCALE.gray100, 0.08)}`,
underline: `0px 1px 0px 0px ${RGBA(GRAY_SCALE.gray100, 0.32)}`,
superHeavy: `2px 4px 16px 0px ${RGBA(
GRAY_SCALE.gray100,
0.12,
)}, 0px 2px 4px 0px ${RGBA(GRAY_SCALE.gray100, 0.04)}`,
};

View File

@ -0,0 +1,21 @@
import { GRAY_SCALE } from './GrayScale';
import { RGBA } from './Rgba';
export const BOX_SHADOW_LIGHT = {
light: `0px 2px 4px 0px ${RGBA(
GRAY_SCALE.gray100,
0.04,
)}, 0px 0px 4px 0px ${RGBA(GRAY_SCALE.gray100, 0.08)}`,
strong: `2px 4px 16px 0px ${RGBA(
GRAY_SCALE.gray100,
0.12,
)}, 0px 2px 4px 0px ${RGBA(GRAY_SCALE.gray100, 0.04)}`,
underline: `0px 1px 0px 0px ${RGBA(GRAY_SCALE.gray100, 0.32)}`,
superHeavy: `0px 0px 8px 0px ${RGBA(
GRAY_SCALE.gray100,
0.16,
)}, 0px 8px 64px -16px ${RGBA(
GRAY_SCALE.gray100,
0.48,
)}, 0px 24px 56px -16px ${RGBA(GRAY_SCALE.gray100, 0.08)}`,
};

View File

@ -0,0 +1,7 @@
import { MAIN_COLORS } from './MainColors';
import { SECONDARY_COLORS } from './SecondaryColors';
export const COLOR = {
...MAIN_COLORS,
...SECONDARY_COLORS,
};

View File

@ -0,0 +1,17 @@
export const FONT_COMMON = {
size: {
xxs: '0.625rem',
xs: '0.85rem',
sm: '0.92rem',
md: '1rem',
lg: '1.23rem',
xl: '1.54rem',
xxl: '1.85rem',
},
weight: {
regular: 400,
medium: 500,
semiBold: 600,
},
family: 'Inter, sans-serif',
};

View File

@ -0,0 +1,16 @@
import { COLOR } from './Colors';
import { FONT_COMMON } from './FontCommon';
import { GRAY_SCALE } from './GrayScale';
export const FONT_DARK = {
color: {
primary: GRAY_SCALE.gray20,
secondary: GRAY_SCALE.gray35,
tertiary: GRAY_SCALE.gray45,
light: GRAY_SCALE.gray50,
extraLight: GRAY_SCALE.gray55,
inverted: GRAY_SCALE.gray100,
danger: COLOR.red,
},
...FONT_COMMON,
};

View File

@ -0,0 +1,16 @@
import { COLOR } from './Colors';
import { FONT_COMMON } from './FontCommon';
import { GRAY_SCALE } from './GrayScale';
export const FONT_LIGHT = {
color: {
primary: GRAY_SCALE.gray60,
secondary: GRAY_SCALE.gray50,
tertiary: GRAY_SCALE.gray40,
light: GRAY_SCALE.gray35,
extraLight: GRAY_SCALE.gray30,
inverted: GRAY_SCALE.gray0,
danger: COLOR.red,
},
...FONT_COMMON,
};

View File

@ -0,0 +1,22 @@
/* eslint-disable @nx/workspace-no-hardcoded-colors */
export const GRAY_SCALE = {
gray100: '#000000',
gray90: '#141414',
gray85: '#171717',
gray80: '#1b1b1b',
gray75: '#1d1d1d',
gray70: '#222222',
gray65: '#292929',
gray60: '#333333',
gray55: '#4c4c4c',
gray50: '#666666',
gray45: '#818181',
gray40: '#999999',
gray35: '#b3b3b3',
gray30: '#cccccc',
gray25: '#d6d6d6',
gray20: '#ebebeb',
gray15: '#f1f1f1',
gray10: '#fcfcfc',
gray0: '#ffffff',
};

View File

@ -0,0 +1,8 @@
import { css } from '@emotion/react';
export const HOVER_BACKGROUND = (props: any) => css`
transition: background 0.1s ease;
&:hover {
background: ${props.theme.background.transparent.light};
}
`;

View File

@ -0,0 +1,13 @@
export const ICON = {
size: {
sm: 14,
md: 16,
lg: 20,
xl: 40,
},
stroke: {
sm: 1.6,
md: 2,
lg: 2.5,
},
};

View File

@ -0,0 +1,5 @@
import { MAIN_COLORS } from './MainColors';
export const MAIN_COLOR_NAMES = Object.keys(MAIN_COLORS) as ThemeColor[];
export type ThemeColor = keyof typeof MAIN_COLORS;

View File

@ -0,0 +1,15 @@
/* eslint-disable @nx/workspace-no-hardcoded-colors */
import { GRAY_SCALE } from './GrayScale';
export const MAIN_COLORS = {
green: '#55ef3c',
turquoise: '#15de8f',
sky: '#00e0ff',
blue: '#1961ed',
purple: '#915ffd',
pink: '#f54bd0',
red: '#f83e3e',
orange: '#ff7222',
yellow: '#ffd338',
gray: GRAY_SCALE.gray30,
};

View File

@ -0,0 +1 @@
export const MOBILE_VIEWPORT = 768;

View File

@ -0,0 +1,7 @@
export const MODAL = {
size: {
sm: '300px',
md: '400px',
lg: '53%',
},
};

View File

@ -0,0 +1,9 @@
import { css } from '@emotion/react';
import { ThemeType } from '..';
export const OVERLAY_BACKGROUND = (props: { theme: ThemeType }) => css`
backdrop-filter: blur(8px);
background: ${props.theme.background.transparent.secondary};
box-shadow: ${props.theme.boxShadow.strong};
`;

View File

@ -0,0 +1,8 @@
/* eslint-disable @nx/workspace-no-hardcoded-colors */
import hexRgb from 'hex-rgb';
export const RGBA = (hex: string, alpha: number) => {
return `rgba(${hexRgb(hex, { format: 'array' })
.slice(0, -1)
.join(',')},${alpha})`;
};

View File

@ -0,0 +1,106 @@
/* eslint-disable @nx/workspace-no-hardcoded-colors */
import { GRAY_SCALE } from './GrayScale';
export const SECONDARY_COLORS = {
yellow80: '#2e2a1a',
yellow70: '#453d1e',
yellow60: '#746224',
yellow50: '#b99b2e',
yellow40: '#ffe074',
yellow30: '#ffedaf',
yellow20: '#fff6d7',
yellow10: '#fffbeb',
green80: '#1d2d1b',
green70: '#23421e',
green60: '#2a5822',
green50: '#42ae31',
green40: '#88f477',
green30: '#ccfac5',
green20: '#ddfcd8',
green10: '#eefdec',
turquoise80: '#172b23',
turquoise70: '#173f2f',
turquoise60: '#166747',
turquoise50: '#16a26b',
turquoise40: '#5be8b1',
turquoise30: '#a1f2d2',
turquoise20: '#d0f8e9',
turquoise10: '#e8fcf4',
sky80: '#152b2e',
sky70: '#123f45',
sky60: '#0e6874',
sky50: '#07a4b9',
sky40: '#4de9ff',
sky30: '#99f3ff',
sky20: '#ccf9ff',
sky10: '#e5fcff',
blue80: '#171e2c',
blue70: '#172642',
blue60: '#18356d',
blue50: '#184bad',
blue40: '#5e90f2',
blue30: '#a3c0f8',
blue20: '#d1dffb',
blue10: '#e8effd',
purple80: '#231e2e',
purple70: '#2f2545',
purple60: '#483473',
purple50: '#6c49b8',
purple40: '#b28ffe',
purple30: '#d3bffe',
purple20: '#e9dfff',
purple10: '#f4efff',
pink80: '#2d1c29',
pink70: '#43213c',
pink60: '#702c61',
pink50: '#b23b98',
pink40: '#f881de',
pink30: '#fbb7ec',
pink20: '#fddbf6',
pink10: '#feedfa',
red80: '#2d1b1b',
red70: '#441f1f',
red60: '#712727',
red50: '#b43232',
red40: '#fa7878',
red30: '#fcb2b2',
red20: '#fed8d8',
red10: '#feecec',
orange80: '#2e2018',
orange70: '#452919',
orange60: '#743b1b',
orange50: '#b9571f',
orange40: '#ff9c64',
orange30: '#ffc7a7',
orange20: '#ffe3d3',
orange10: '#fff1e9',
gray80: GRAY_SCALE.gray70,
gray70: GRAY_SCALE.gray65,
gray60: GRAY_SCALE.gray55,
gray50: GRAY_SCALE.gray40,
gray40: GRAY_SCALE.gray25,
gray30: GRAY_SCALE.gray20,
gray20: GRAY_SCALE.gray15,
gray10: GRAY_SCALE.gray10,
blueAccent90: '#141a25',
blueAccent85: '#151d2e',
blueAccent80: '#152037',
blueAccent75: '#16233f',
blueAccent70: '#17294a',
blueAccent60: '#18356d',
blueAccent40: '#a3c0f8',
blueAccent35: '#c8d9fb',
blueAccent25: '#dae6fc',
blueAccent20: '#e2ecfd',
blueAccent15: '#edf2fe',
blueAccent10: '#f5f9fd',
};

View File

@ -0,0 +1,28 @@
import { COLOR } from './Colors';
export const TAG_DARK = {
text: {
green: COLOR.green10,
turquoise: COLOR.turquoise10,
sky: COLOR.sky10,
blue: COLOR.blue10,
purple: COLOR.purple10,
pink: COLOR.pink10,
red: COLOR.red10,
orange: COLOR.orange10,
yellow: COLOR.yellow10,
gray: COLOR.gray10,
},
background: {
green: COLOR.green60,
turquoise: COLOR.turquoise60,
sky: COLOR.sky60,
blue: COLOR.blue60,
purple: COLOR.purple60,
pink: COLOR.pink60,
red: COLOR.red60,
orange: COLOR.orange60,
yellow: COLOR.yellow60,
gray: COLOR.gray60,
},
};

View File

@ -0,0 +1,28 @@
import { COLOR } from './Colors';
export const TAG_LIGHT = {
text: {
green: COLOR.green60,
turquoise: COLOR.turquoise60,
sky: COLOR.sky60,
blue: COLOR.blue60,
purple: COLOR.purple60,
pink: COLOR.pink60,
red: COLOR.red60,
orange: COLOR.orange60,
yellow: COLOR.yellow60,
gray: COLOR.gray60,
},
background: {
green: COLOR.green20,
turquoise: COLOR.turquoise20,
sky: COLOR.sky20,
blue: COLOR.blue20,
purple: COLOR.purple20,
pink: COLOR.pink20,
red: COLOR.red20,
orange: COLOR.orange20,
yellow: COLOR.yellow20,
gray: COLOR.gray20,
},
};

View File

@ -0,0 +1,13 @@
export const TEXT = {
lineHeight: {
lg: 1.5,
md: 1.2,
},
iconSizeMedium: 16,
iconSizeSmall: 14,
iconStrikeLight: 1.6,
iconStrikeMedium: 2,
iconStrikeBold: 2.5,
};

View File

@ -0,0 +1,21 @@
import { css } from '@emotion/react';
import { ThemeType } from '..';
export const TEXT_INPUT_STYLE = (props: { theme: ThemeType }) => css`
background-color: transparent;
border: none;
color: ${props.theme.font.color.primary};
font-family: ${props.theme.font.family};
font-size: inherit;
font-weight: inherit;
outline: none;
padding: ${props.theme.spacing(0)} ${props.theme.spacing(2)};
&::placeholder,
&::-webkit-input-placeholder {
color: ${props.theme.font.color.light};
font-family: ${props.theme.font.family};
font-weight: ${props.theme.font.weight.medium};
}
`;

View File

@ -0,0 +1,44 @@
/* eslint-disable @nx/workspace-no-hardcoded-colors */
import { ANIMATION } from './Animation';
import { BLUR } from './Blur';
import { COLOR } from './Colors';
import { GRAY_SCALE } from './GrayScale';
import { ICON } from './Icon';
import { MODAL } from './Modal';
import { TEXT } from './Text';
export const THEME_COMMON = {
color: COLOR,
grayScale: GRAY_SCALE,
icon: ICON,
modal: MODAL,
text: TEXT,
blur: BLUR,
animation: ANIMATION,
snackBar: {
success: {
background: '#16A26B',
color: '#D0F8E9',
},
error: {
background: '#B43232',
color: '#FED8D8',
},
info: {
background: COLOR.gray80,
color: GRAY_SCALE.gray0,
},
},
spacingMultiplicator: 4,
spacing: (...args: number[]) =>
args.map((multiplicator) => `${multiplicator * 4}px`).join(' '),
betweenSiblingsGap: `2px`,
table: {
horizontalCellMargin: '8px',
checkboxColumnWidth: '32px',
horizontalCellPadding: '8px',
},
rightDrawerWidth: '500px',
clickableElementBackgroundTransition: 'background 0.1s ease',
lastLayerZIndex: 2147483647,
};

View File

@ -0,0 +1,22 @@
import { ThemeType } from '..';
import { ACCENT_DARK } from './AccentDark';
import { BACKGROUND_DARK } from './BackgroundDark';
import { BORDER_DARK } from './BorderDark';
import { BOX_SHADOW_DARK } from './BoxShadowDark';
import { FONT_DARK } from './FontDark';
import { TAG_DARK } from './TagDark';
import { THEME_COMMON } from './ThemeCommon';
export const THEME_DARK: ThemeType = {
...THEME_COMMON,
...{
accent: ACCENT_DARK,
background: BACKGROUND_DARK,
border: BORDER_DARK,
tag: TAG_DARK,
boxShadow: BOX_SHADOW_DARK,
font: FONT_DARK,
name: 'dark',
},
};

View File

@ -0,0 +1,20 @@
import { ACCENT_LIGHT } from './AccentLight';
import { BACKGROUND_LIGHT } from './BackgroundLight';
import { BORDER_LIGHT } from './BorderLight';
import { BOX_SHADOW_LIGHT } from './BoxShadowLight';
import { FONT_LIGHT } from './FontLight';
import { TAG_LIGHT } from './TagLight';
import { THEME_COMMON } from './ThemeCommon';
export const THEME_LIGHT = {
...THEME_COMMON,
...{
accent: ACCENT_LIGHT,
background: BACKGROUND_LIGHT,
border: BORDER_LIGHT,
tag: TAG_LIGHT,
boxShadow: BOX_SHADOW_LIGHT,
font: FONT_LIGHT,
name: 'light',
},
};

View File

@ -0,0 +1,5 @@
import { THEME_DARK } from './constants/ThemeDark';
import { THEME_LIGHT } from './constants/ThemeLight';
export type ThemeType = typeof THEME_LIGHT;
export { THEME_DARK, THEME_LIGHT };

View File

@ -0,0 +1,16 @@
// ThemeProvider.tsx
import * as React from 'react';
import { ThemeProvider as EmotionThemeProvider } from '@emotion/react';
import { ThemeType } from '..';
type ThemeProviderProps = {
theme: ThemeType;
children: React.ReactNode;
};
const ThemeProvider: React.FC<ThemeProviderProps> = ({ theme, children }) => {
return <EmotionThemeProvider theme={theme}>{children}</EmotionThemeProvider>;
};
export default ThemeProvider;

View File

@ -0,0 +1,26 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"jsx": "react"
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./tsconfig.storybook.json"
}
]
}

View File

@ -0,0 +1,18 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node", "vite/client"]
},
"include": ["src/**/*.ts"],
"exclude": [
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"**/*.stories.ts",
"**/*.stories.js",
"**/*.stories.jsx",
"**/*.stories.tsx"
]
}

View File

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,32 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"emitDecoratorMetadata": true,
"outDir": ""
},
"files": [
"../../node_modules/@nx/react/typings/styled-jsx.d.ts",
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
"../../node_modules/@nx/react/typings/image.d.ts"
],
"exclude": [
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/**/*.spec.js",
"src/**/*.test.js",
"src/**/*.spec.tsx",
"src/**/*.test.tsx",
"src/**/*.spec.jsx",
"src/**/*.test.js"
],
"include": [
"src/**/*.stories.ts",
"src/**/*.stories.js",
"src/**/*.stories.jsx",
"src/**/*.stories.tsx",
"src/**/*.stories.mdx",
".storybook/*.js",
".storybook/*.ts",
".storybook/main.tsx"
]
}

View File

@ -0,0 +1,47 @@
/// <reference types='vitest' />
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import * as path from 'path';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
export default defineConfig({
root: __dirname,
cacheDir: '../../node_modules/.vite/packages/twenty-ui',
plugins: [
nxViteTsPaths(),
dts({
entryRoot: 'src',
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
skipDiagnostics: true,
}),
],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'twenty-ui',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs'],
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: [],
},
},
});

View File

@ -15,7 +15,8 @@
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"twenty-emails": ["packages/twenty-emails/src/index.ts"]
"twenty-emails": ["packages/twenty-emails/src/index.ts"],
"twenty-ui": ["packages/twenty-ui/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]

1671
yarn.lock

File diff suppressed because it is too large Load Diff