From 7d3ae26395b912622543261767581cd589ae0802 Mon Sep 17 00:00:00 2001 From: lisa Date: Tue, 18 Nov 2025 16:59:17 +0100 Subject: [PATCH] refactor: remove emotion from storybook and ui --- .changeset/nine-moons-wink.md | 6 + .storybook/.babelrc | 14 +- .storybook/components/DocsContainer.tsx | 13 +- .storybook/components/Page.tsx | 75 +---- .storybook/components/globalStyle.css.ts | 82 +++++ .storybook/components/globalStyle.ts | 39 --- .storybook/preview.tsx | 15 +- .storybook/vite.config.ts | 4 - babel.config.json | 4 +- e2e/tsconfig.json | 3 +- e2e/vite.config.ts | 9 +- eslint.config.mjs | 5 +- examples/next-app-router/emotion.d.ts | 9 - examples/next-app-router/tsconfig.json | 1 - examples/next/src/components/Header.tsx | 6 +- .../next/src/pages/advanced/Introduction.tsx | 6 +- global.d.ts | 2 - package.json | 7 +- packages/themes/package.json | 3 +- packages/themes/src/vanilla/globalStyle.css | 184 ++---------- packages/ui/package.json | 5 - .../src/__stories__/Properties/Properties.tsx | 13 +- packages/ui/src/__stories__/Testing.mdx | 2 + .../ThemeGenerator/FormContent/index.tsx | 42 +-- .../ThemeGenerator/FormContent/style.css.ts | 11 + .../ThemeResult/CodeIntegration.tsx | 16 +- .../Tools/ThemeGenerator/ThemeResult/Demo.tsx | 42 +-- .../ThemeGenerator/ThemeResult/index.tsx | 13 +- .../ThemeGenerator/ThemeResult/styles.css.ts | 23 ++ .../assets/screen-shot-example.png | Bin 47512 -> 0 bytes .../ui/src/__stories__/components/Colors.tsx | 173 ++++++----- .../__stories__/components/ThemeWrapper.tsx | 8 +- .../src/__stories__/components/styles.css.ts | 26 ++ packages/ui/src/__stories__/theme/colors.mdx | 2 +- .../ui/src/__stories__/theme/darkMode.mdx | 2 +- packages/ui/src/__stories__/theme/shadows.mdx | 2 +- packages/ui/src/__stories__/theme/spaces.mdx | 2 +- .../ui/src/__stories__/theme/typography.mdx | 2 +- .../__stories__/theme/understandTokens.mdx | 2 +- .../Button/__stories__/Showcase.stories.tsx | 26 +- .../Button/__stories__/style.css.ts | 17 ++ .../Carousel/__stories__/Template.stories.tsx | 34 +-- .../Carousel/__stories__/styles.css.ts | 10 + .../CopyButton/__stories__/Sizes.stories.tsx | 9 +- .../__stories__/SelectInput.stories.tsx | 10 +- .../InfiniteScroll/__stories__/style.css.ts | 6 + packages/ui/src/components/Loader/index.tsx | 1 - .../__stories__/PerPage.stories.tsx | 13 +- .../Pagination/__stories__/styles.css.ts | 9 + .../Row/__stories__/AlignItems.stories.tsx | 30 +- .../__stories__/DivWithBackground.css.ts | 9 +- .../Row/__stories__/DivWithBackground.ts | 12 - .../Row/__stories__/Padding.stories.tsx | 10 +- .../Row/__stories__/Responsive.stories.tsx | 18 +- .../Row/__stories__/Template.stories.tsx | 26 +- .../Stack/__stories__/AlignItems.stories.tsx | 2 - .../Stack/__stories__/Direction.stories.tsx | 14 +- .../Stack/__stories__/Responsive.stories.tsx | 8 +- .../Stack/__stories__/Template.stories.tsx | 8 +- .../components/Stack/__stories__/helper.tsx | 37 +-- .../Stack/__stories__/styles.css.ts | 36 +++ .../Text/__stories__/OneLine.stories.tsx | 18 +- .../components/Text/__stories__/style.css.ts | 10 + packages/ui/src/emotion.d.ts | 6 - .../ui/src/helpers/__tests__/treeMap.test.ts | 4 +- packages/ui/src/helpers/treeMap.ts | 3 +- packages/ui/src/index.ts | 1 - packages/ui/src/utils/index.ts | 2 +- .../ui/src/utils/responsive/Breakpoint.tsx | 13 - packages/ui/src/utils/responsive/index.ts | 1 - packages/ui/src/utils/responsive/style.css.ts | 8 + packages/ui/tsconfig.json | 2 +- pnpm-lock.yaml | 279 +----------------- tsconfig.json | 1 - utils/scripts/analyse-deps.ts | 11 +- vite.config.ts | 5 - 76 files changed, 587 insertions(+), 995 deletions(-) create mode 100644 .changeset/nine-moons-wink.md create mode 100644 .storybook/components/globalStyle.css.ts delete mode 100644 .storybook/components/globalStyle.ts delete mode 100644 examples/next-app-router/emotion.d.ts create mode 100644 packages/ui/src/__stories__/Tools/ThemeGenerator/FormContent/style.css.ts create mode 100644 packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/styles.css.ts delete mode 100644 packages/ui/src/__stories__/assets/screen-shot-example.png create mode 100644 packages/ui/src/__stories__/components/styles.css.ts create mode 100644 packages/ui/src/components/Button/__stories__/style.css.ts create mode 100644 packages/ui/src/components/Carousel/__stories__/styles.css.ts create mode 100644 packages/ui/src/components/InfiniteScroll/__stories__/style.css.ts create mode 100644 packages/ui/src/components/Pagination/__stories__/styles.css.ts rename packages/ui/src/components/{Stack => Row}/__stories__/DivWithBackground.css.ts (75%) delete mode 100644 packages/ui/src/components/Row/__stories__/DivWithBackground.ts create mode 100644 packages/ui/src/components/Stack/__stories__/styles.css.ts create mode 100644 packages/ui/src/components/Text/__stories__/style.css.ts delete mode 100644 packages/ui/src/emotion.d.ts delete mode 100644 packages/ui/src/utils/responsive/Breakpoint.tsx create mode 100644 packages/ui/src/utils/responsive/style.css.ts diff --git a/.changeset/nine-moons-wink.md b/.changeset/nine-moons-wink.md new file mode 100644 index 0000000000..8545cc9586 --- /dev/null +++ b/.changeset/nine-moons-wink.md @@ -0,0 +1,6 @@ +--- +"@ultraviolet/ui": major +--- + +- Remove `Emotion` +- Removed `Breakpoint`: use directly `up` and `down` diff --git a/.storybook/.babelrc b/.storybook/.babelrc index 1bebc8abf6..047386a449 100644 --- a/.storybook/.babelrc +++ b/.storybook/.babelrc @@ -1,14 +1,5 @@ { - "plugins": [ - [ - "@emotion", - { - "sourceMap": false, - "autoLabel": "never", - "labelFormat": "[filename]--[local]" - } - ] - ], + "presets": [ "@babel/preset-typescript", [ @@ -18,8 +9,7 @@ [ "@babel/preset-react", { - "runtime": "automatic", - "importSource": "@emotion/react" + "runtime": "automatic" } ] ] diff --git a/.storybook/components/DocsContainer.tsx b/.storybook/components/DocsContainer.tsx index f9df16b6b3..f49bfe293a 100644 --- a/.storybook/components/DocsContainer.tsx +++ b/.storybook/components/DocsContainer.tsx @@ -1,4 +1,3 @@ -import { Global, ThemeProvider } from '@emotion/react' import type { DocsContainerProps as BaseContainerProps} from '@storybook/addon-docs/blocks'; import { @@ -8,9 +7,10 @@ import { import { consoleLightTheme as lightTheme, ThemeProvider as ThemeProviderUV } from '@ultraviolet/themes' import type { ReactNode } from 'react' import { cloneElement, isValidElement, useState } from 'react' -import { globalStyles } from './globalStyle' import '@ultraviolet/fonts/fonts.css' import { GlobalAlert } from '@ultraviolet/ui' +import "../../packages/themes/dist/themes.css" +import { globalStyleStoryBook } from './globalStyle.css'; type ExtraProps = { /** @@ -66,8 +66,8 @@ const DocsContainer = ({ children, context }: DocsContainerProps) => { return ( - - +
+ {isBeta ? { > A Beta version is available. Please use this version if your dependencies include the Beta release. : null} - {isValidElement(children) ? cloneElement(children, { @@ -88,8 +87,8 @@ const DocsContainer = ({ children, context }: DocsContainerProps) => { }) : children} - - + +
) } diff --git a/.storybook/components/Page.tsx b/.storybook/components/Page.tsx index 127b19a49e..c47ad22560 100644 --- a/.storybook/components/Page.tsx +++ b/.storybook/components/Page.tsx @@ -1,4 +1,3 @@ -import styled from '@emotion/styled' import { linkTo } from '@storybook/addon-links' import { Controls, @@ -9,59 +8,7 @@ import { Title, } from '@storybook/addon-docs/blocks' import { Alert, Stack, Text } from '@ultraviolet/ui' -import background from '../assets/brand-background.png' - -const TitleDecorator = styled.div` - h1 { - color: ${({ theme }) => theme.colors.neutral.text}; - font-size: ${({ theme }) => theme.typography.headingLargeStronger.fontSize}; - font-family: ${({ theme }) => theme.typography.headingLargeStronger.fontFamily}; - font-weight: ${({ theme }) => theme.typography.headingLargeStronger.weight}; - letter-spacing: ${({ theme }) => theme.typography.headingLargeStronger.letterSpacing}; - line-height: ${({ theme }) => theme.typography.headingLargeStronger.lineHeight}; - text-transform: ${({ theme }) => theme.typography.headingLargeStronger.textCase}; - text-decoration: ${({ theme }) => theme.typography.headingLargeStronger.textDecoration}; - padding: ${({ theme }) => theme.space[5]} ${({ theme }) => theme.space[3]}; - border-radius: ${({ theme }) => theme.radii.default}; - background-image: url(${background}); - background-size: cover; - background-position: center; - background-repeat: no-repeat; - width: 100%; - } -` - -const StyledH2 = styled(Text)` - padding-bottom: ${({ theme }) => theme.space[1]}; - margin-bottom: ${({ theme }) => theme.space[2]}; - border-bottom: 2px solid ${({ theme }) => theme.colors.neutral.borderStronger}; -` - -const StoriesDecorator = styled(Stack)` - #stories { - color: ${({ theme }) => theme.colors.neutral.text}; - font-size: ${({ theme }) => theme.typography.headingStrong.fontSize}; - font-family: ${({ theme }) => theme.typography.headingStrong.fontFamily}; - font-weight: ${({ theme }) => theme.typography.headingStrong.weight}; - letter-spacing: ${({ theme }) => theme.typography.headingStrong.letterSpacing}; - line-height: ${({ theme }) => theme.typography.headingStrong.lineHeight}; - text-transform: ${({ theme }) => theme.typography.headingStrong.textCase}; - text-decoration: ${({ theme }) => theme.typography.headingStrong.textDecoration}; - padding-bottom: ${({ theme }) => theme.space[1]}; - border-bottom: 2px solid ${({ theme }) => theme.colors.neutral.borderStronger}; - margin-bottom: ${({ theme }) => theme.space[2]}; - } -` - -const StoriesThemes = styled.div` - .docs-story > div { - padding: 0; - } - - .css-xzp052 .innerZoomElementWrapper > * { - border: 0 !important; - } -` +import { h2Decorator, storiesDecorator, storiesTheme, titleDecorator } from './globalStyle.css' type PageProps = { deprecated?: boolean @@ -78,12 +25,12 @@ const Page = ({ hideArgsTable, experimental, }: PageProps) => ( - +
- +
- </TitleDecorator> + </div> {deprecated ? ( <Alert title="Deprecated component" @@ -107,9 +54,9 @@ const Page = ({ </div> <Stack gap={2}> <Stack> - <StyledH2 as="h2" variant="headingStrong"> + <Text as="h2" className={h2Decorator} variant="headingStrong"> Overview - </StyledH2> + </Text> <Subtitle /> <Description /> </Stack> @@ -117,19 +64,19 @@ const Page = ({ <Primary /> {!hideArgsTable ? ( <> - <StyledH2 as="h2" variant="headingStrong"> + <Text as="h2" className={h2Decorator} variant="headingStrong"> Props - </StyledH2> + </Text> <Controls /> </> ) : null} </div> - <StoriesDecorator> + <Stack className={storiesDecorator}> <Stories /> - </StoriesDecorator> + </Stack> </Stack> </Stack> - </StoriesThemes> + </div> ) export default Page diff --git a/.storybook/components/globalStyle.css.ts b/.storybook/components/globalStyle.css.ts new file mode 100644 index 0000000000..acfbe5a938 --- /dev/null +++ b/.storybook/components/globalStyle.css.ts @@ -0,0 +1,82 @@ +import { consoleLightTheme as theme } from '@ultraviolet/themes' +import { globalStyle, style } from '@vanilla-extract/css' +import background from '../assets/brand-background.png' + +export const globalStyleStoryBook = style({ + fontFamily: "'Inter', sans-serif", + fontSize: 16, + + color: theme.colors.neutral.text +}) + +globalStyle(`${globalStyleStoryBook} p`, { margin: 0 }) + + +globalStyle(`${globalStyleStoryBook} h2, ${globalStyleStoryBook} h3, ${globalStyleStoryBook} h4, ${globalStyleStoryBook} h5, ${globalStyleStoryBook} h6`, { margin: `${theme.space[2]} 0 ${theme.space[1]} 0`, + }) + +globalStyle(`${globalStyleStoryBook} .sb-anchor h1, ${globalStyleStoryBook} .sb-anchor h2, ${globalStyleStoryBook} .sb-anchor h3, ${globalStyleStoryBook} .sb-anchor h4, ${globalStyleStoryBook} .sb-anchor h5, ${globalStyleStoryBook} .sb-anchor h6`, { margin: "inherit" }) + +globalStyle(`${globalStyleStoryBook} body`, { + fontFamily: "'Inter', sans-serif", + fontSize: 16, + fontWeight: 400, + lineHeight: 24, + color: theme.colors.neutral.text + }) + + +globalStyle(`${globalStyleStoryBook} h1, h2, h3, h4, h5, h3`, { + color: theme.colors.neutral.text + }) + +globalStyle(`${globalStyleStoryBook} .toc-list-item::before`, { borderColor: `${theme.colors.primary.border} !important` }) + +globalStyle(`${globalStyleStoryBook} .toc-list-item`, { paddingBottom: "3px !important" }) + +globalStyle(`${globalStyleStoryBook} .toc-list-item.is-active-li>a`, { color: `${theme.colors.primary.text} !important` }) + +export const titleDecorator = style({}) + +globalStyle(`${titleDecorator} > h1`, { + color: theme.colors.neutral.text, + fontSize: theme.typography.headingLargeStronger.fontSize, + fontFamily: theme.typography.headingLargeStronger.fontFamily, + fontWeight: theme.typography.headingLargeStronger.weight, + letterSpacing: theme.typography.headingLargeStronger.letterSpacing, + lineHeight: theme.typography.headingLargeStronger.lineHeight, + textDecoration: theme.typography.headingLargeStronger.textDecoration, + padding: `${theme.space[5]} ${theme.space[3]}`, + borderRadius: theme.radii.default, + backgroundImage: `url(${background})`, + backgroundSize: "cover", + backgroundPosition: "center", + backgroundRepeat: 'no-repeat', + width: "100%", +}) + +export const h2Decorator = style({ + paddingBottom: theme.space[1], + marginBottom: `${theme.space[2]} !important`, + borderBottom: `2px solid ${theme.colors.neutral.borderStronger}` +}) + +export const storiesDecorator = style({}) + +globalStyle(`${storiesDecorator} #stories`, { + color: theme.colors.neutral.text, + fontSize: theme.typography.headingStrong.fontSize, + fontFamily: theme.typography.headingStrong.fontFamily, + fontWeight: theme.typography.headingStrong.weight, + letterSpacing: theme.typography.headingStrong.letterSpacing, + lineHeight: theme.typography.headingStrong.lineHeight, + textDecoration: theme.typography.headingStrong.textDecoration, + paddingBottom: theme.space[1], + borderBottom: `2px solid ${theme.colors.neutral.borderStronger}`, + marginBottom: theme.space[2], +}) +export const storiesTheme = style({}) + +globalStyle(`${storiesTheme} .docs-story > div`, { padding: 0}) + +globalStyle(`${storiesTheme} .css-xzp052 .innerZoomElementWrapper > *`, { border: "0 !important"}) diff --git a/.storybook/components/globalStyle.ts b/.storybook/components/globalStyle.ts deleted file mode 100644 index 221c943b08..0000000000 --- a/.storybook/components/globalStyle.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { css } from '@emotion/react' -import { normalize } from '@ultraviolet/ui' -import {space, colors} from '../../packages/ui/src/theme' - -export const globalStyles = css` - ${normalize()} - - p { - margin: 0; - } - - h1 { - margin: ${space['5']} 0; - } - - h2, h3, h4, h5, h6 { - margin: ${space['2']} 0 ${space['1']} 0; - } - - .sb-anchor h1, .sb-anchor h2, .sb-anchor h3, .sb-anchor h4, .sb-anchor h5, .sb-anchor h6 { - margin: inherit; - } - - body { - font-family: 'Inter', sans-serif; - font-size: 16px; - font-weight: 400; - line-height: 24px; - color: ${colors.neutral.text}; - } - - .toc-list-item::before { - border-color: ${colors.primary.border} !important; - } - - .toc-list-item.is-active-li>a { - color: ${colors.primary.text} !important; - } -` diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 3096cadd61..dba274dc96 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,5 +1,3 @@ -import { Global, ThemeProvider } from '@emotion/react' -import { withThemeFromJSXProvider } from '@storybook/addon-themes' import type { Preview, StoryFn } from '@storybook/react-vite' import { themes } from 'storybook/theming' import { @@ -10,9 +8,9 @@ import { } from '@ultraviolet/themes' import DocsContainer from './components/DocsContainer' import Page from './components/Page' -import { globalStyles } from './components/globalStyle' import { dark, light } from './storybookThemes' import '@ultraviolet/fonts/fonts.css' + import { scan } from "react-scan" const BREAKPOINT_ORDER = [ @@ -166,16 +164,7 @@ const decorators = [ </ThemeProviderUI> </> )}, // Storybook is broken without this please refer to this issue: https://github.com/storybookjs/storybook/issues/24625 - withThemeFromJSXProvider({ - themes: { - light: lightTheme, - dark: darkTheme, - darker: darkerTheme, - }, - defaultTheme: 'light', - Provider: ThemeProvider, - GlobalStyles: () => <Global styles={[globalStyles]} />, - }), + withThemeProvider, ] diff --git a/.storybook/vite.config.ts b/.storybook/vite.config.ts index a9dcbe7f54..cbd0e69b05 100644 --- a/.storybook/vite.config.ts +++ b/.storybook/vite.config.ts @@ -48,10 +48,6 @@ export default defineConfig({ svgr({ memo: true, svgo: false }), react({ jsxRuntime: 'automatic', - jsxImportSource: '@emotion/react', - babel: { - plugins: ['@emotion/babel-plugin'], - }, }), vanillaExtractPlugin({}) ], diff --git a/babel.config.json b/babel.config.json index e27d9b78bf..fcf3b2e696 100644 --- a/babel.config.json +++ b/babel.config.json @@ -1,5 +1,4 @@ { - "plugins": ["@emotion"], "presets": [ "@babel/preset-typescript", [ @@ -9,8 +8,7 @@ [ "@babel/preset-react", { - "runtime": "automatic", - "importSource": "@emotion/react" + "runtime": "automatic" } ] ] diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index 33e8472d87..fe13c13e8b 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -5,7 +5,6 @@ "target": "esnext", "module": "esnext", "jsx": "preserve", - "jsxImportSource": "@emotion/react", "allowImportingTsExtensions": true, "forceConsistentCasingInFileNames": true, "noPropertyAccessFromIndexSignature": false, @@ -14,6 +13,6 @@ "skipLibCheck": true, "types": ["vite/client"] }, - "include": ["global.d.ts", "emotion.d.ts", "**/*.ts", "**/*.tsx"], + "include": ["global.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules", "coverage", "dist"] } diff --git a/e2e/vite.config.ts b/e2e/vite.config.ts index 16a2f2e488..5c746232d1 100644 --- a/e2e/vite.config.ts +++ b/e2e/vite.config.ts @@ -7,14 +7,7 @@ export default defineConfig({ external: ['fsevents'], }, }, - plugins: [ - react({ - babel: { - plugins: ['@emotion/babel-plugin'], - }, - jsxImportSource: '@emotion/react', - }), - ], + plugins: [react()], server: { // Sends all requests to index.html if file not found fs: { diff --git a/eslint.config.mjs b/eslint.config.mjs index 6aa0865923..dfe1f6800f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,7 +3,6 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' import babelParser from '@babel/eslint-parser' -import scwEmotion from '@scaleway/eslint-config-react/emotion' import scwJavascript from '@scaleway/eslint-config-react/javascript' import scwTypescript from '@scaleway/eslint-config-react/typescript' import oxlint from 'eslint-plugin-oxlint' @@ -76,7 +75,7 @@ export default [ }, }, }, - ...[...scwJavascript, ...scwEmotion].map(config => ({ + ...scwJavascript.map(config => ({ ...config, languageOptions: { parser: babelParser, @@ -98,7 +97,7 @@ export default [ }, }, }, - ...[...scwTypescript, ...scwEmotion].map(config => ({ + ...scwTypescript.map(config => ({ ...config, files: ['**/*.{ts,tsx}'], rules: { diff --git a/examples/next-app-router/emotion.d.ts b/examples/next-app-router/emotion.d.ts deleted file mode 100644 index f44242cfea..0000000000 --- a/examples/next-app-router/emotion.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import '@emotion/react' -import type { consoleLightTheme } from '@ultraviolet/themes' - -type CustomTheme = typeof consoleLightTheme - -declare module '@emotion/react' { - // eslint-disable-next-line @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-empty-interface - export interface Theme extends CustomTheme {} -} diff --git a/examples/next-app-router/tsconfig.json b/examples/next-app-router/tsconfig.json index 532910fb90..232cff3c44 100644 --- a/examples/next-app-router/tsconfig.json +++ b/examples/next-app-router/tsconfig.json @@ -27,7 +27,6 @@ "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", - "emotion.d.ts" ], "exclude": ["node_modules"] } diff --git a/examples/next/src/components/Header.tsx b/examples/next/src/components/Header.tsx index 6dbd94371e..d07acbf67a 100644 --- a/examples/next/src/components/Header.tsx +++ b/examples/next/src/components/Header.tsx @@ -1,6 +1,6 @@ import { MoonIcon, SunIcon } from '@ultraviolet/icons' import { useTheme } from '@ultraviolet/themes' -import { Breakpoint, Toggle } from '@ultraviolet/ui' +import { Toggle } from '@ultraviolet/ui' import styles from '../../styles/component.module.scss' import GithubAndDocumentationButtons from './GithubAndDocumentationButtons' import Logo from './Logo' @@ -15,9 +15,7 @@ const TopBar = ({ setTheme }: { setTheme: (theme: Themes) => void }) => { <div className={styles.headerRow}> <Logo /> <div className={styles.horizontalStack}> - <Breakpoint up="medium"> - <GithubAndDocumentationButtons /> - </Breakpoint> + <GithubAndDocumentationButtons /> <SunIcon size="small" /> <Toggle checked={theme === 'dark'} diff --git a/examples/next/src/pages/advanced/Introduction.tsx b/examples/next/src/pages/advanced/Introduction.tsx index a3d05395de..894fc939d9 100644 --- a/examples/next/src/pages/advanced/Introduction.tsx +++ b/examples/next/src/pages/advanced/Introduction.tsx @@ -1,4 +1,4 @@ -import { Breakpoint, Stack, Text } from '@ultraviolet/ui' +import { Stack, Text } from '@ultraviolet/ui' import Image from 'next/image' import styles from '../../../styles/advanced.module.scss' import swA11y from '../../assets/icons/icon-scaleway-a11y.svg' @@ -17,9 +17,7 @@ const Introduction = () => ( gap={2} justifyContent="space-between" > - <Breakpoint down="medium"> - <GithubAndDocumentationButtons /> - </Breakpoint> + <GithubAndDocumentationButtons /> <Stack> <Text as="h1" sentiment="primary" variant="headingLarge"> Scaleway <b>UI</b> diff --git a/global.d.ts b/global.d.ts index 21e49791c4..ca1ee35e7b 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,5 +1,3 @@ -/// <reference types="@emotion/react/types/css-prop" /> - declare module '*.svg' { import type { FunctionComponent, SVGProps } from 'react' diff --git a/package.json b/package.json index bf259a7ab1..f30653d83c 100644 --- a/package.json +++ b/package.json @@ -104,12 +104,6 @@ "@changesets/cli": "2.29.7", "@commitlint/cli": "20.1.0", "@commitlint/config-conventional": "20.0.0", - "@emotion/babel-plugin": "11.13.5", - "@emotion/cache": "11.14.0", - "@emotion/eslint-plugin": "11.12.0", - "@emotion/jest": "11.13.0", - "@emotion/react": "11.14.0", - "@emotion/styled": "11.14.1", "@eslint/compat": "1.4.1", "@eslint/eslintrc": "3.3.1", "@manypkg/cli": "0.25.1", @@ -137,6 +131,7 @@ "@ultraviolet/fonts": "workspace:*", "@ultraviolet/themes": "workspace:*", "@ultraviolet/ui": "workspace:*", + "@vanilla-extract/css": "1.17.4", "@vanilla-extract/vite-plugin": "5.1.1", "@vitejs/plugin-react": "5.0.4", "@vitest/coverage-istanbul": "3.2.4", diff --git a/packages/themes/package.json b/packages/themes/package.json index 1dbb12aae6..9f004a2fdb 100644 --- a/packages/themes/package.json +++ b/packages/themes/package.json @@ -53,7 +53,8 @@ "require": "./dist/themes/console/*/index.cjs", "import": "./dist/themes/console/*/index.js" }, - "./global": "./dist/themes.css" + "./global": "./dist/themes.css", + "./theme.css": "./dist/themes.css" }, "peerDependencies": { "react": "18.x || 19.x", diff --git a/packages/themes/src/vanilla/globalStyle.css b/packages/themes/src/vanilla/globalStyle.css index 4f52698834..2ec488c87d 100644 --- a/packages/themes/src/vanilla/globalStyle.css +++ b/packages/themes/src/vanilla/globalStyle.css @@ -1,156 +1,28 @@ -img, -canvas, -iframe, -video, -select, -textarea { - max-width: 100%; -} - -p, -h1, -h2, -h3, -h4, -h5, -h6 { - margin: 0; -} - -*, -::after, -::before { - box-sizing: border-box -} - -html { - -webkit-text-size-adjust: 100%; - -moz-tab-size: 4; - tab-size: 4; - font-size: 16px; - line-height: 1.5; - -webkit-tap-highlight-color: transparent; - margin: 0; -} - -body { - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - margin: 0; - font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji' -} - -hr { - height: 0; - color: inherit -} - -abbr[title] { - text-decoration: underline dotted -} - -b, -strong { - font-weight: bolder -} - -code, -kbd, -pre, -samp { - font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; - font-size: 1em -} - -small { - font-size: 80% -} - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline -} - -sub { - bottom: -.25em -} - -sup { - top: -.5em -} - -table { - text-indent: 0; - border-color: inherit -} - -button, -input, -optgroup, -select, -textarea { - font-family: inherit; - font-size: 100%; - line-height: 1.15; - margin: 0 -} - -button, -select { - text-transform: none -} - -[type=button], -[type=reset], -[type=submit], -button { - -webkit-appearance: button -} - -::-moz-focus-inner { - border-style: none; - padding: 0 -} - -:-moz-focusring { - outline: 1px dotted ButtonText -} - -:-moz-ui-invalid { - box-shadow: none -} - -legend { - padding: 0 -} - -progress { - vertical-align: baseline -} - -::-webkit-inner-spin-button, -::-webkit-outer-spin-button { - height: auto -} - -[type=search] { - -webkit-appearance: textfield; - outline-offset: -2px -} - -::-webkit-search-decoration { - -webkit-appearance: none -} - -::-webkit-file-upload-button { - -webkit-appearance: button; - font: inherit -} - -summary { - display: list-item -} \ No newline at end of file +*,::after,::before{box-sizing:border-box}html{-moz-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji'}hr{height:0;color:inherit}abbr[title]{text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Consolas,'Liberation Mono',Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item} + + html { + font-size: 16px; + line-height: 1.5; + -webkit-tap-highlight-color: transparent; + margin: 0; + } + + body { + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + img, + canvas, + iframe, + video, + select, + textarea { + max-width: 100%; + } + + p, h1, h2, h3, h4, h5, h6 { + margin: 0; + } + \ No newline at end of file diff --git a/packages/ui/package.json b/packages/ui/package.json index 212ebbe819..f989b581bb 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -71,15 +71,11 @@ } ], "peerDependencies": { - "@emotion/react": "11.14.0", - "@emotion/styled": "11.14.1", "react": "18.x || 19.x", "react-dom": "18.x || 19.x" }, "devDependencies": { "@babel/core": "7.28.5", - "@emotion/react": "11.14.0", - "@emotion/styled": "11.14.1", "@types/react": "19.2.2", "@types/react-dom": "19.2.2", "@utils/test": "workspace:*", @@ -88,7 +84,6 @@ "react-dom": "19.2.0" }, "dependencies": { - "@emotion/serialize": "1.3.3", "@nivo/bar": "0.89.1", "@nivo/core": "0.89.1", "@nivo/line": "0.89.1", diff --git a/packages/ui/src/__stories__/Properties/Properties.tsx b/packages/ui/src/__stories__/Properties/Properties.tsx index 0575fb92d1..04aa67bf15 100644 --- a/packages/ui/src/__stories__/Properties/Properties.tsx +++ b/packages/ui/src/__stories__/Properties/Properties.tsx @@ -1,14 +1,9 @@ -import styled from '@emotion/styled' import type { ComponentType } from 'react' import * as components from '../../components' import { Stack } from '../../components/Stack' import { Table } from '../../components/Table' import { Text } from '../../components/Text' -const StyledTableRow = styled(Table.Row)` - vertical-align: top; -` - type PropertyType = { defaultValue: { value: string @@ -233,7 +228,11 @@ const Properties = () => { ] return ( - <StyledTableRow id={property} key={property}> + <Table.Row + id={property} + key={property} + style={{ verticalAlign: 'top' }} + > <Table.Cell> <Text as="span" variant="bodyStrong"> {property} @@ -264,7 +263,7 @@ const Properties = () => { ].components.join(', ')} </Text> </Table.Cell> - </StyledTableRow> + </Table.Row> ) }, )} diff --git a/packages/ui/src/__stories__/Testing.mdx b/packages/ui/src/__stories__/Testing.mdx index c638e5f937..8d072616e3 100644 --- a/packages/ui/src/__stories__/Testing.mdx +++ b/packages/ui/src/__stories__/Testing.mdx @@ -34,6 +34,8 @@ As stated in [`@testing-library/react`](https://testing-library.com/docs/dom-tes This will wrap the provided node with a ThemeProvider and render it + + This is useful when you need to dig through a rendered node ```jsx diff --git a/packages/ui/src/__stories__/Tools/ThemeGenerator/FormContent/index.tsx b/packages/ui/src/__stories__/Tools/ThemeGenerator/FormContent/index.tsx index e27aa78743..7c1f618aa9 100644 --- a/packages/ui/src/__stories__/Tools/ThemeGenerator/FormContent/index.tsx +++ b/packages/ui/src/__stories__/Tools/ThemeGenerator/FormContent/index.tsx @@ -1,4 +1,3 @@ -import styled from '@emotion/styled' import { CheckIcon, CloseIcon, RestoreIcon } from '@ultraviolet/icons' import { useState } from 'react' import { @@ -10,16 +9,7 @@ import { } from '../../../../../../form/src' import { Button, Row, Stack, Text, Tooltip } from '../../../../components' import { hexadecimalColorRegex, INITIAL_VALUES } from '../contants' - -const CapitalizeText = styled(Text)` - &::first-letter { - text-transform: capitalize; - } -` - -const StyledRow = styled(Row)` - width: 100%; -` +import { capitalizeText, row } from './style.css' export const FormContent = () => { const [confirmResetForm, setConfirmResetForm] = useState(false) @@ -33,13 +23,14 @@ export const FormContent = () => { <Stack gap={6}> <Stack direction="row" gap={1}> <Stack flex={1} gap={1}> - <CapitalizeText + <Text as="label" + className={capitalizeText} htmlFor="sentiment_neutral" variant="bodyStrong" > Neutral sentiment name - </CapitalizeText> + </Text> <Tooltip text="Neutral sentiment name cannot be changed as it is essential for the theme to work."> <Stack flex={1}> <TextInputField @@ -53,13 +44,14 @@ export const FormContent = () => { </Tooltip> </Stack> <Stack flex={1} gap={1}> - <CapitalizeText + <Text as="label" + className={capitalizeText} htmlFor="sentiment_neutral_value" variant="bodyStrong" > Neutral sentiment value - </CapitalizeText> + </Text> <Tooltip text="Neutral sentiment value cannot be changed as it is essential for the theme to work."> <Stack flex={1}> <TextInputField @@ -86,8 +78,9 @@ export const FormContent = () => { return ( <Stack direction="row" gap={1} key={field.id}> <Stack flex={1} gap={1}> - <CapitalizeText + <Text as="label" + className={capitalizeText} htmlFor={`sentiments.${index}.key`} variant="bodyStrong" > @@ -96,7 +89,7 @@ export const FormContent = () => { : `Additional sentiment ${ index - countRequiredSentiments + 1 } name`} - </CapitalizeText> + </Text> <Tooltip text={ isRequiredSentiment @@ -116,8 +109,9 @@ export const FormContent = () => { </Tooltip> </Stack> <Stack flex={1} gap={1}> - <CapitalizeText + <Text as="label" + className={capitalizeText} htmlFor={`sentiments.${index}.value`} variant="bodyStrong" > @@ -126,22 +120,16 @@ export const FormContent = () => { : `Additional sentiment ${ index - countRequiredSentiments + 1 } value`} - </CapitalizeText> + </Text> <Stack alignItems="center" direction="row" gap={1}> - <StyledRow gap={1} templateColumns="9fr 1fr"> - <TextInputField - id={`sentiments.${index}.value`} - name={`sentiments.${index}.value`} - placeholder="#FFFFFF" - regex={[hexadecimalColorRegex]} - /> + <Row className={row} gap={1} templateColumns="9fr 1fr"> <TextInputField id={`sentiments.${index}.value`} name={`sentiments.${index}.value`} placeholder="#FFFFFF" regex={[hexadecimalColorRegex]} /> - </StyledRow> + </Row> {!isRequiredSentiment ? ( <Button onClick={() => remove(index)} diff --git a/packages/ui/src/__stories__/Tools/ThemeGenerator/FormContent/style.css.ts b/packages/ui/src/__stories__/Tools/ThemeGenerator/FormContent/style.css.ts new file mode 100644 index 0000000000..51a81e6ea8 --- /dev/null +++ b/packages/ui/src/__stories__/Tools/ThemeGenerator/FormContent/style.css.ts @@ -0,0 +1,11 @@ +import { style } from '@vanilla-extract/css' + +export const capitalizeText = style({ + selectors: { + '&::first-letter': { + textTransform: 'capitalize', + }, + }, +}) + +export const row = style({ width: '100%' }) diff --git a/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/CodeIntegration.tsx b/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/CodeIntegration.tsx index 5f5fb741ad..bcd29fa9be 100644 --- a/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/CodeIntegration.tsx +++ b/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/CodeIntegration.tsx @@ -1,27 +1,21 @@ -import styled from '@emotion/styled' import { useState } from 'react' import { Snippet, Stack, Tabs, Text } from '../../../../components' import type { UltravioletUITheme } from '../../../../theme' +import { snippetResult } from './styles.css' type CodeIntegrationProps = { theme: UltravioletUITheme } -const StyledSnippet = styled(Snippet)` - pre { - padding: ${({ theme }) => theme.space['2']}; - } -` - const reactCode = (theme: string) => ` -import { Global, ThemeProvider, css } from '@emotion/react' import { Button, normalize } from '@ultraviolet/ui' +import { ThemeProvider } from "@ultraviolet/themes" +import "@ultraviolet/themes/global" const THEME = ${theme} const App = () => ( <ThemeProvider theme={THEME}> - <Global styles={css\`\${normalize()}\`} /> <Button onClick={() => console.log('clicked')}> Click Me </Button> @@ -48,9 +42,9 @@ export const CodeIntegration = ({ theme }: CodeIntegrationProps) => { <Tabs.Tab value={1}>JSON</Tabs.Tab> <Tabs.Tab value={2}>React</Tabs.Tab> </Tabs> - <StyledSnippet prefix="lines"> + <Snippet className={snippetResult} prefix="lines"> {tabState === 1 ? formattedTheme : reactCode(formattedTheme)} - </StyledSnippet> + </Snippet> </Stack> </Stack> ) diff --git a/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/Demo.tsx b/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/Demo.tsx index 67152c93cf..6fb8c411d2 100644 --- a/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/Demo.tsx +++ b/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/Demo.tsx @@ -1,4 +1,3 @@ -import styled from '@emotion/styled' import type { ChangeEvent } from 'react' import { useState } from 'react' import { @@ -27,25 +26,12 @@ import { Text, Toggle, } from '../../../../components' - -const StyledStepper = styled(Stepper)` - padding: 0 ${({ theme }) => theme.space['2']}; -` - -const StyledStepList = styled(StepList)` - margin: 0; -` - -const StyledStack = styled(Stack)` - gap: 6px; // This has to be custom because we need both row to be same size and this value doesn't exists in theme -` - -const Container = styled.div` - background: ${({ theme }) => theme.colors.neutral.background}; - box-shadow: ${({ theme }) => theme.shadows.hoverNeutral}; - border-radius: ${({ theme }) => theme.radii.large}; - padding: ${({ theme }) => theme.space['4']}; -` +import { + themeGeneratorContainer, + themeGeneratorStack, + themeGeneratorStepList, + themeGeneratorStepper, +} from './styles.css' export const Demo = () => { const [tabState, setTabState] = useState<number | string>(1) @@ -57,7 +43,7 @@ export const Demo = () => { const [selectableCardState, setSelectableCardState] = useState('option-1') return ( - <Container> + <div className={themeGeneratorContainer}> <Row gap={2} templateColumns="1fr 1fr"> <Stack gap={2}> <Tabs @@ -111,11 +97,11 @@ export const Demo = () => { </Row> </Stack> </Card> - <StyledStepper selected={1}> + <Stepper className={themeGeneratorStepper} selected={1}> <span>Initialize</span> <span>Create</span> <span>Done</span> - </StyledStepper> + </Stepper> <Stack direction="row" gap={1}> <Badge sentiment="neutral">UV-UI</Badge> <Badge sentiment="primary">UV-CORE</Badge> @@ -135,7 +121,7 @@ export const Demo = () => { /> </Card> <Card> - <StyledStepList> + <StepList className={themeGeneratorStepList}> <StepList.Item bulletContent={<CheckIcon />} sentiment="success" @@ -150,7 +136,7 @@ export const Demo = () => { > You have 10 days of trial </StepList.Item> - </StyledStepList> + </StepList> </Card> </Stack> <Stack gap={1}> @@ -178,14 +164,14 @@ export const Demo = () => { > <Stack alignItems="center" direction="row" gap={2}> <Avatar shape="circle" text="MA" variant="text" /> - <StyledStack> + <Stack className={themeGeneratorStack}> <Text as="span" sentiment="primary" variant="bodySmall"> Review from Marc - 2 days ago </Text> <Text as="span" variant="body"> Ultraviolet is the best UI library I... </Text> - </StyledStack> + </Stack> </Stack> <Button sentiment="neutral" size="small" variant="ghost"> <ArrowRightIcon /> @@ -249,6 +235,6 @@ export const Demo = () => { </Stack> </Stack> </Row> - </Container> + </div> ) } diff --git a/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/index.tsx b/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/index.tsx index d45a961c6b..cb136c33c9 100644 --- a/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/index.tsx +++ b/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/index.tsx @@ -1,19 +1,12 @@ -import { ThemeProvider } from '@emotion/react' -import styled from '@emotion/styled' import { ArrowLeftIcon, EyeIcon, EyeOffIcon } from '@ultraviolet/icons' +import { ThemeProvider } from '@ultraviolet/themes' import { useCallback, useState } from 'react' import { Button, Row, Stack, Text } from '../../../../components' import type { UltravioletUITheme } from '../../../../theme' import consoleLightTheme from '../../../../theme' -import { normalize } from '../../../../utils' import { CodeIntegration } from './CodeIntegration' import { Demo } from './Demo' // For some reason Global doesn't work here this is the workaround I found -// For some reason Global doesn't work here this is the workaround I found -const Container = styled.div` - ${normalize()} -` - type ThemeResultProps = { theme: UltravioletUITheme setTheme: (theme: UltravioletUITheme) => void @@ -72,9 +65,7 @@ export const ThemeResult = ({ </Row> <Stack gap={4}> <ThemeProvider theme={theme}> - <Container> - <Demo /> - </Container> + <Demo /> </ThemeProvider> <CodeIntegration theme={generatedPalette} /> </Stack> diff --git a/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/styles.css.ts b/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/styles.css.ts new file mode 100644 index 0000000000..7e7df5459e --- /dev/null +++ b/packages/ui/src/__stories__/Tools/ThemeGenerator/ThemeResult/styles.css.ts @@ -0,0 +1,23 @@ +import { theme } from '@ultraviolet/themes' +import { globalStyle, style } from '@vanilla-extract/css' + +export const snippetResult = style({}) + +globalStyle(`${snippetResult} pre`, { + padding: theme.space[2], +}) + +export const themeGeneratorStepper = style({ + padding: `0 ${theme.space[2]}`, +}) + +export const themeGeneratorStepList = style({ margin: 0 }) + +export const themeGeneratorStack = style({ gap: 6 }) + +export const themeGeneratorContainer = style({ + background: theme.colors.neutral.background, + boxShadow: theme.shadows.hoverNeutral, + borderRadius: theme.radii.large, + padding: theme.space[4], +}) diff --git a/packages/ui/src/__stories__/assets/screen-shot-example.png b/packages/ui/src/__stories__/assets/screen-shot-example.png deleted file mode 100644 index b32744372d6552dc244075dff3ae69ce6681b2b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47512 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 LMFXpT>*0R^Kh#yX diff --git a/packages/ui/src/__stories__/components/Colors.tsx b/packages/ui/src/__stories__/components/Colors.tsx index 2c6819c213..d9e8688618 100644 --- a/packages/ui/src/__stories__/components/Colors.tsx +++ b/packages/ui/src/__stories__/components/Colors.tsx @@ -1,5 +1,5 @@ -import styled from '@emotion/styled' import { useTheme } from '@ultraviolet/themes' +import { assignInlineVars } from '@vanilla-extract/dynamic' import { Row } from '../../components' import { Card } from '../../components/Card' import { Separator } from '../../components/Separator' @@ -7,38 +7,16 @@ import { Stack } from '../../components/Stack' import { Text } from '../../components/Text' import type lightTheme from '../../theme' import type { Color } from '../../theme' +import { + capitalizedText, + card, + computedBackground, + noMarginText, + paddingCard, + separator, +} from './styles.css' import ThemeWrapper from './ThemeWrapper' -const StyledSeparator = styled(Separator)` - margin: ${({ theme }) => `${theme.space['3']} 0`}; -` - -const CapitalizedText = styled(Text)` - text-transform: capitalize; -` - -const NoMarginText = styled(Text)` - margin: 0; -` - -const StyledCard = styled(Card, { - shouldForwardProp: prop => - !['sentiment', 'context', 'color', 'padding'].includes(prop), -})<{ - sentiment?: Color - color?: string - context: keyof (typeof lightTheme)['colors'][Color] - padding?: string -}>` - align-items: center; - background: ${({ sentiment, context, theme, color }) => - sentiment ? theme.colors[sentiment][context] : color}; - display: flex; - justify-content: space-between; - padding: ${({ padding }) => padding ?? '8px'}; - width: 100%; -` - type AvailableContexts = keyof (typeof lightTheme)['colors'][Color] const Colors = () => { @@ -64,9 +42,13 @@ const Colors = () => { return ( <Stack gap={1} key={sentiment}> - <CapitalizedText as="h3" variant="headingSmallStrong"> + <Text + as="h3" + className={capitalizedText} + variant="headingSmallStrong" + > {sentiment} - </CapitalizedText> + </Text> <Row gap={2} templateColumns="repeat(3, 1fr)"> <Stack direction="column" gap={2}> {colorContextKeys @@ -86,13 +68,16 @@ const Colors = () => { </Text> </Stack> - <StyledCard - context={context} + <Card + className={`${card} ${paddingCard.default}`} key={context} - sentiment={sentiment} + style={assignInlineVars({ + [computedBackground]: + theme.colors[sentiment][context], + })} > {' '} - </StyledCard> + </Card> </Stack> ))} </Stack> @@ -113,9 +98,15 @@ const Colors = () => { {theme.colors[sentiment][context]} </Text> </Stack> - <StyledCard context={context} sentiment={sentiment}> + <Card + className={`${card} ${paddingCard.default}`} + style={assignInlineVars({ + [computedBackground]: + theme.colors[sentiment][context], + })} + > {' '} - </StyledCard> + </Card> </Stack> ))} </Stack> @@ -136,14 +127,20 @@ const Colors = () => { {theme.colors[sentiment][context]} </Text> </Stack> - <StyledCard context={context} sentiment={sentiment}> + <Card + className={`${card} ${paddingCard.default}`} + style={assignInlineVars({ + [computedBackground]: + theme.colors[sentiment][context], + })} + > {' '} - </StyledCard> + </Card> </Stack> ))} </Stack> </Row> - <StyledSeparator /> + <Separator className={separator} /> </Stack> ) })} @@ -171,9 +168,14 @@ const Colors = () => { {dataColors[data]} </Text> </Stack> - <StyledCard color={dataColors[data]} context="background"> + <Card + className={`${card} ${paddingCard.default}`} + style={assignInlineVars({ + [computedBackground]: dataColors[data], + })} + > {' '} - </StyledCard> + </Card> </Stack> ), )} @@ -208,29 +210,36 @@ const Colors = () => { gradientBackgroundColors[ type as keyof typeof gradientBackgroundColors ], - ).map(background => ( - <Stack key={background}> - <Stack - alignItems="center" - direction="row" - justifyContent="space-between" - > - <Text as="p" variant="body"> - {background} - </Text> + ).map(background => { + // @ts-expect-error can't infer properly + const gradient = gradientBackgroundColors[type][ + background + ].replace(/;$/, '') + + return ( + <Stack key={background}> + <Stack + alignItems="center" + direction="row" + justifyContent="space-between" + > + <Text as="p" variant="body"> + {background} + </Text> + </Stack> + <Card + className={`${card} ${paddingCard.large}`} + style={{ + ...assignInlineVars({ + [computedBackground]: gradient as string, + }), + }} + > + {' '} + </Card> </Stack> - <StyledCard - color={ - // @ts-expect-error can't infer properly - gradientBackgroundColors[type][background] - } - context="background" - padding="32px" - > - {' '} - </StyledCard> - </Stack> - ))} + ) + })} </Row> </Stack> ))} @@ -249,9 +258,13 @@ const Colors = () => { {Object.keys(iconColors[type as keyof typeof iconColors]).map( sentiment => ( <Stack direction="column" key={sentiment}> - <NoMarginText as="h4" variant="bodyStrong"> + <Text + as="h4" + className={noMarginText} + variant="bodyStrong" + > {sentiment} - </NoMarginText> + </Text> <Row gap={2} templateColumns="repeat(3, 1fr)"> {Object.keys( // @ts-expect-error can't infer properly @@ -264,26 +277,30 @@ const Colors = () => { direction="row" justifyContent="space-between" > - <NoMarginText as="p" variant="body"> + <Text + as="p" + className={noMarginText} + variant="body" + > {value} - </NoMarginText> + </Text> <Text as="small" variant="caption"> { // @ts-expect-error can't infer properly - iconColors[type][sentiment][value] } </Text> </Stack> - <StyledCard - color={ - // @ts-expect-error can't infer properly - iconColors[type][sentiment][value] - } - context="background" + <Card + className={`${card} ${paddingCard.default}`} + style={assignInlineVars({ + [computedBackground]: + // @ts-expect-error can't infer properly + iconColors[type][sentiment][value], + })} > {' '} - </StyledCard> + </Card> </Stack> ))} </Row> diff --git a/packages/ui/src/__stories__/components/ThemeWrapper.tsx b/packages/ui/src/__stories__/components/ThemeWrapper.tsx index 83843b028c..5b854d776c 100644 --- a/packages/ui/src/__stories__/components/ThemeWrapper.tsx +++ b/packages/ui/src/__stories__/components/ThemeWrapper.tsx @@ -1,13 +1,9 @@ -import { Global, ThemeProvider } from '@emotion/react' +import { ThemeProvider } from '@ultraviolet/themes' import type { ReactNode } from 'react' -import { globalStyles } from '../../../../../.storybook/components/globalStyle' import lightTheme from '../../theme' const ThemeWrapper = ({ children }: { children: ReactNode }) => ( - <ThemeProvider theme={lightTheme}> - <Global styles={[globalStyles]} /> - {children} - </ThemeProvider> + <ThemeProvider theme={lightTheme}>{children}</ThemeProvider> ) export default ThemeWrapper diff --git a/packages/ui/src/__stories__/components/styles.css.ts b/packages/ui/src/__stories__/components/styles.css.ts new file mode 100644 index 0000000000..738eaec97c --- /dev/null +++ b/packages/ui/src/__stories__/components/styles.css.ts @@ -0,0 +1,26 @@ +import { theme } from '@ultraviolet/themes' +import { createVar, style, styleVariants } from '@vanilla-extract/css' + +export const computedBackground = createVar() +export const separator = style({ + margin: `${theme.space[3]} 0`, +}) + +export const capitalizedText = style({ + textTransform: 'capitalize', +}) + +export const noMarginText = style({ margin: 0 }) + +export const card = style({ + alignItems: 'center', + display: 'flex', + justifyContent: 'space-between', + width: '100%', + padding: 8, + background: computedBackground, +}) +export const paddingCard = styleVariants({ + default: { padding: 8 }, + large: { padding: 32 }, +}) diff --git a/packages/ui/src/__stories__/theme/colors.mdx b/packages/ui/src/__stories__/theme/colors.mdx index b719e6b292..bf48e9a472 100644 --- a/packages/ui/src/__stories__/theme/colors.mdx +++ b/packages/ui/src/__stories__/theme/colors.mdx @@ -14,7 +14,7 @@ to your Theme Provider. You will need to pass an object with the same structure as the [default theme](https://github.com/scaleway/ultraviolet/blob/main/src/theme/tokens/light.ts), but with your own colors. ```tsx -import { ThemeProvider } from '@emotion/react' +import { ThemeProvider } from '@ultraviolet/themes' import { Badge, extendTheme } from '@ultraviolet/ui' function App() { diff --git a/packages/ui/src/__stories__/theme/darkMode.mdx b/packages/ui/src/__stories__/theme/darkMode.mdx index 7f712eba12..e47fdf5e5b 100644 --- a/packages/ui/src/__stories__/theme/darkMode.mdx +++ b/packages/ui/src/__stories__/theme/darkMode.mdx @@ -12,7 +12,7 @@ You can enable easily switch between light and dark mode by passing `theme` or ` Example: ```tsx -import { ThemeProvider } from '@emotion/react' +import { ThemeProvider } from '@ultraviolet/themes' import { theme, darkTheme, Button, Text } from '@ultraviolet/ui' import { useState } from 'react' diff --git a/packages/ui/src/__stories__/theme/shadows.mdx b/packages/ui/src/__stories__/theme/shadows.mdx index ddb8371e45..f3ef0d5a6f 100644 --- a/packages/ui/src/__stories__/theme/shadows.mdx +++ b/packages/ui/src/__stories__/theme/shadows.mdx @@ -12,7 +12,7 @@ You may also want to customize your shadows. To do that you need to create your You will need to pass an object with the same structure as the [default theme](https://github.com/scaleway/ultraviolet/blob/main/src/theme/tokens/light.ts), but with your own typography. ```tsx -import { ThemeProvider } from '@emotion/react' +import { ThemeProvider } from '@ultraviolet/themes' import { Tooltip, extendTheme } from '@ultraviolet/ui' function App() { diff --git a/packages/ui/src/__stories__/theme/spaces.mdx b/packages/ui/src/__stories__/theme/spaces.mdx index 8e190e01d4..3726a1fd6a 100644 --- a/packages/ui/src/__stories__/theme/spaces.mdx +++ b/packages/ui/src/__stories__/theme/spaces.mdx @@ -71,7 +71,7 @@ const MyComponent = styled.div` You can customize the space and sizing by passing a custom theme to the `ThemeProvider`: ```tsx -import { ThemeProvider } from '@emotion/react' +import { ThemeProvider } from '@ultraviolet/themes' import { Button, extendTheme } from '@ultraviolet/ui' function App() { diff --git a/packages/ui/src/__stories__/theme/typography.mdx b/packages/ui/src/__stories__/theme/typography.mdx index b7b2e24cce..469c98891c 100644 --- a/packages/ui/src/__stories__/theme/typography.mdx +++ b/packages/ui/src/__stories__/theme/typography.mdx @@ -13,7 +13,7 @@ You may also want to customize your typography fonts or sizes. To do that you ne You will need to pass an object with the same structure as the [default theme](https://github.com/scaleway/ultraviolet/blob/main/src/theme/tokens/light.ts), but with your own typography. ```tsx -import { css, Global, ThemeProvider } from '@emotion/react' +import { css, Global, ThemeProvider } from '@ultraviolet/themes' import { Text, extendTheme } from '@ultraviolet/ui' import AsapRegularWoff2 from 'assets/fonts/asap/Asap-Regular.woff2' diff --git a/packages/ui/src/__stories__/theme/understandTokens.mdx b/packages/ui/src/__stories__/theme/understandTokens.mdx index e3ef824a44..ae1fd525ba 100644 --- a/packages/ui/src/__stories__/theme/understandTokens.mdx +++ b/packages/ui/src/__stories__/theme/understandTokens.mdx @@ -6,7 +6,7 @@ import ThemeWrapper from '../components/ThemeWrapper' # Colors -Ultraviolet UI is based on `@emotion/react` library. +Ultraviolet UI is based on `vanilla-extract` library. ## Structure diff --git a/packages/ui/src/components/Button/__stories__/Showcase.stories.tsx b/packages/ui/src/components/Button/__stories__/Showcase.stories.tsx index 58e7dccd9d..2874a11aca 100644 --- a/packages/ui/src/components/Button/__stories__/Showcase.stories.tsx +++ b/packages/ui/src/components/Button/__stories__/Showcase.stories.tsx @@ -1,27 +1,11 @@ -import styled from '@emotion/styled' import type { StoryFn } from '@storybook/react-vite' -import type { ExtendedColor } from '../../../theme' import { SENTIMENTS } from '../../../theme' import { Stack, Table, Text } from '../..' import { Button } from '..' +import { showCase } from './style.css' const buttonVariants = ['ghost', 'filled', 'outlined'] as const -const StyledRow = styled(Table.Row, { - shouldForwardProp: prop => !['sentiment'].includes(prop), -})<{ sentiment: ExtendedColor }>` - background: ${({ sentiment }) => { - if (sentiment === 'white') { - return 'black' - } - if (sentiment === 'black') { - return 'white' - } - - return 'none' - }}; -` - const COLUMNS = [ { label: '' }, ...buttonVariants.map(variant => ({ @@ -35,7 +19,11 @@ export const Showcase: StoryFn<typeof Button> = args => ( <Table columns={COLUMNS}> <Table.Body> {([...SENTIMENTS, 'white', 'black'] as const).map(sentiment => ( - <StyledRow id={sentiment} key={sentiment} sentiment={sentiment}> + <Table.Row + className={showCase[sentiment]} + id={sentiment} + key={sentiment} + > <Table.Cell> <Text as="span" @@ -63,7 +51,7 @@ export const Showcase: StoryFn<typeof Button> = args => ( </Stack> </Table.Cell> ))} - </StyledRow> + </Table.Row> ))} </Table.Body> </Table> diff --git a/packages/ui/src/components/Button/__stories__/style.css.ts b/packages/ui/src/components/Button/__stories__/style.css.ts new file mode 100644 index 0000000000..5022e9c289 --- /dev/null +++ b/packages/ui/src/components/Button/__stories__/style.css.ts @@ -0,0 +1,17 @@ +import { styleVariants } from '@vanilla-extract/css' +import { SENTIMENTS } from '../../../theme' + +export const showCase = styleVariants( + Object.fromEntries( + [...SENTIMENTS, 'white', 'black'].map(sentiment => { + if (sentiment === 'white') { + return [sentiment, { background: 'black' }] + } + if (sentiment === 'black') { + return [sentiment, { background: 'white' }] + } + + return [sentiment, { background: 'none' }] + }), + ), +) diff --git a/packages/ui/src/components/Carousel/__stories__/Template.stories.tsx b/packages/ui/src/components/Carousel/__stories__/Template.stories.tsx index 7ab37a08ea..da26f12f6e 100644 --- a/packages/ui/src/components/Carousel/__stories__/Template.stories.tsx +++ b/packages/ui/src/components/Carousel/__stories__/Template.stories.tsx @@ -1,64 +1,56 @@ -import styled from '@emotion/styled' import type { StoryFn } from '@storybook/react-vite' import { Carousel } from '..' - -const Content = styled.div` - background-color: ${({ theme }) => theme.colors.info.background}; - width: 100%; - padding: ${({ theme }) => theme.space['3']}; - color: ${({ theme }) => theme.colors.info.text}; - text-align: center; -` +import { carouselStoryContent } from './styles.css' export const Template: StoryFn<typeof Carousel> = props => ( <Carousel {...props}> <Carousel.Item> - <Content> + <div className={carouselStoryContent}> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam - </Content> + </div> </Carousel.Item> <Carousel.Item> - <Content> + <div className={carouselStoryContent}> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - </Content> + </div> </Carousel.Item> <Carousel.Item> - <Content> + <div className={carouselStoryContent}> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. - </Content> + </div> </Carousel.Item> <Carousel.Item> - <Content> + <div className={carouselStoryContent}> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. - </Content> + </div> </Carousel.Item> <Carousel.Item> - <Content> + <div className={carouselStoryContent}> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. - </Content> + </div> </Carousel.Item> <Carousel.Item> - <Content> + <div className={carouselStoryContent}> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. - </Content> + </div> </Carousel.Item> </Carousel> ) diff --git a/packages/ui/src/components/Carousel/__stories__/styles.css.ts b/packages/ui/src/components/Carousel/__stories__/styles.css.ts new file mode 100644 index 0000000000..49f755bc3b --- /dev/null +++ b/packages/ui/src/components/Carousel/__stories__/styles.css.ts @@ -0,0 +1,10 @@ +import { theme } from '@ultraviolet/themes' +import { style } from '@vanilla-extract/css' + +export const carouselStoryContent = style({ + backgroundColor: theme.colors.info.background, + width: '100%', + padding: theme.space[3], + color: theme.colors.info.text, + textAlign: 'center', +}) diff --git a/packages/ui/src/components/CopyButton/__stories__/Sizes.stories.tsx b/packages/ui/src/components/CopyButton/__stories__/Sizes.stories.tsx index 1741281cc6..e90e55d54e 100644 --- a/packages/ui/src/components/CopyButton/__stories__/Sizes.stories.tsx +++ b/packages/ui/src/components/CopyButton/__stories__/Sizes.stories.tsx @@ -1,20 +1,15 @@ -import styled from '@emotion/styled' import type { ComponentProps } from 'react' import { Stack } from '../../Stack' import { Text } from '../../Text' import { CopyButton } from '../index' -const StyledText = styled(Text)` - min-width: 80px; -` - export const Sizes = (props: ComponentProps<typeof CopyButton>) => ( <Stack gap={1}> {(['xsmall', 'small', 'medium', 'large'] as const).map(size => ( <Stack direction="row" gap={1} key={size}> - <StyledText as="span" variant="body"> + <Text as="span" style={{ minWidth: 80 }} variant="body"> {size}: - </StyledText> + </Text> <CopyButton {...props} size={size} value="Text that will be copied" /> </Stack> ))} diff --git a/packages/ui/src/components/InfiniteScroll/__stories__/SelectInput.stories.tsx b/packages/ui/src/components/InfiniteScroll/__stories__/SelectInput.stories.tsx index 63228dd065..9caff8a946 100644 --- a/packages/ui/src/components/InfiniteScroll/__stories__/SelectInput.stories.tsx +++ b/packages/ui/src/components/InfiniteScroll/__stories__/SelectInput.stories.tsx @@ -1,4 +1,3 @@ -import styled from '@emotion/styled' import type { StoryFn } from '@storybook/react-vite' import { useState } from 'react' import { SelectInput as SelectInputUV } from '../../SelectInput' @@ -6,17 +5,14 @@ import { Skeleton } from '../../Skeleton' import { Stack } from '../../Stack' import { InfiniteScroll } from '..' import { generateRandomNamesArray, SELECT_INPUT_DATA } from './data' - -const StyledStack = styled(Stack)` - padding: 0 ${({ theme }) => theme.space[3]}; -` +import { infiniteScrollSelectInput } from './style.css' const InfiniteScrollLoader = ( - <StyledStack direction="column" gap="3"> + <Stack className={infiniteScrollSelectInput} direction="column" gap="3"> <Skeleton variant="line" /> <Skeleton variant="line" /> <Skeleton variant="line" /> - </StyledStack> + </Stack> ) export const SelectInput: StoryFn<typeof InfiniteScroll> = args => { diff --git a/packages/ui/src/components/InfiniteScroll/__stories__/style.css.ts b/packages/ui/src/components/InfiniteScroll/__stories__/style.css.ts new file mode 100644 index 0000000000..2705701f48 --- /dev/null +++ b/packages/ui/src/components/InfiniteScroll/__stories__/style.css.ts @@ -0,0 +1,6 @@ +import { theme } from '@ultraviolet/themes' +import { style } from '@vanilla-extract/css' + +export const infiniteScrollSelectInput = style({ + padding: `0 ${theme.space[3]}`, +}) diff --git a/packages/ui/src/components/Loader/index.tsx b/packages/ui/src/components/Loader/index.tsx index b8bb3fd8c2..6a5fc3d9a3 100644 --- a/packages/ui/src/components/Loader/index.tsx +++ b/packages/ui/src/components/Loader/index.tsx @@ -66,7 +66,6 @@ export const Loader = ({ /> <circle className={loaderCircleAnimation} - // oxlint-disable-next-line no-unknown-property cx={HALF_VIEWBOX_WIDTH} cy={HALF_VIEWBOX_HEIGHT} fill="none" diff --git a/packages/ui/src/components/Pagination/__stories__/PerPage.stories.tsx b/packages/ui/src/components/Pagination/__stories__/PerPage.stories.tsx index b1172b57e8..48283778bf 100644 --- a/packages/ui/src/components/Pagination/__stories__/PerPage.stories.tsx +++ b/packages/ui/src/components/Pagination/__stories__/PerPage.stories.tsx @@ -1,17 +1,12 @@ -import styled from '@emotion/styled' import type { StoryFn } from '@storybook/react-vite' import { useState } from 'react' import { Badge } from '../../Badge' import { Stack } from '../../Stack' import { Pagination } from '..' +import { list } from './styles.css' const NUMBER_OF_ITEMS = 134 -const StyledList = styled.ul` - height: 13.5rem; - overflow-y: auto; - border: ${({ theme }) => theme.colors.neutral.border} 1px solid; - padding: ${({ theme }) => theme.space[1]}; -` + export const PerPage: StoryFn = props => { const [page, setPage] = useState(1) const [perPage, setPerPage] = useState(10) @@ -19,7 +14,7 @@ export const PerPage: StoryFn = props => { return ( <Stack gap={1}> - <StyledList> + <ul className={list}> {Array.from({ length: perPage }).map((_, index) => { const itemNumber = perPage * (page - 1) + index + 1 if (itemNumber <= NUMBER_OF_ITEMS) { @@ -28,7 +23,7 @@ export const PerPage: StoryFn = props => { return null })} - </StyledList> + </ul> <Pagination {...props} numberOfItems={NUMBER_OF_ITEMS} diff --git a/packages/ui/src/components/Pagination/__stories__/styles.css.ts b/packages/ui/src/components/Pagination/__stories__/styles.css.ts new file mode 100644 index 0000000000..ef56d63284 --- /dev/null +++ b/packages/ui/src/components/Pagination/__stories__/styles.css.ts @@ -0,0 +1,9 @@ +import { theme } from '@ultraviolet/themes' +import { style } from '@vanilla-extract/css' + +export const list = style({ + height: '13.5rem', + overflowY: 'auto', + border: `1px solid ${theme.colors.neutral.border}`, + padding: theme.space[1], +}) diff --git a/packages/ui/src/components/Row/__stories__/AlignItems.stories.tsx b/packages/ui/src/components/Row/__stories__/AlignItems.stories.tsx index 33a88a8374..324bcf261c 100644 --- a/packages/ui/src/components/Row/__stories__/AlignItems.stories.tsx +++ b/packages/ui/src/components/Row/__stories__/AlignItems.stories.tsx @@ -3,7 +3,7 @@ import { Separator } from '../../Separator' import { Stack } from '../../Stack' import { Text } from '../../Text' import { Row } from '..' -import { DivWithBackground } from './DivWithBackground' +import { divWithBackground } from './DivWithBackground.css' export const AlignItems: StoryFn = args => ( <Stack gap={2}> @@ -11,27 +11,37 @@ export const AlignItems: StoryFn = args => ( alignItems=“start“ </Text> <Row {...args} alignItems="start" gap={1} templateColumns="repeat(3, 1fr)"> - <DivWithBackground style={{ height: '100px' }}>1fr</DivWithBackground> - <DivWithBackground style={{ height: '50px' }}>1fr</DivWithBackground> - <DivWithBackground>1fr</DivWithBackground> + <div style={{ height: '100px' }}>1fr</div> + <div className={divWithBackground} style={{ height: '50px' }}> + 1fr + </div> + <div className={divWithBackground}>1fr</div> </Row> <Separator /> <Text as="p" variant="bodyStrong"> alignItems=“center“ </Text> <Row {...args} alignItems="center" gap={1} templateColumns="repeat(3, 1fr)"> - <DivWithBackground style={{ height: '100px' }}>1fr</DivWithBackground> - <DivWithBackground style={{ height: '50px' }}>1fr</DivWithBackground> - <DivWithBackground>1fr</DivWithBackground> + <div className={divWithBackground} style={{ height: '100px' }}> + 1fr + </div> + <div className={divWithBackground} style={{ height: '50px' }}> + 1fr + </div> + <div className={divWithBackground}>1fr</div> </Row> <Separator /> <Text as="p" variant="bodyStrong"> alignItems=“end“ </Text> <Row {...args} alignItems="end" gap={1} templateColumns="repeat(3, 1fr)"> - <DivWithBackground style={{ height: '100px' }}>1fr</DivWithBackground> - <DivWithBackground style={{ height: '50px' }}>1fr</DivWithBackground> - <DivWithBackground>1fr</DivWithBackground> + <div className={divWithBackground} style={{ height: '100px' }}> + 1fr + </div> + <div className={divWithBackground} style={{ height: '50px' }}> + 1fr + </div> + <div className={divWithBackground}>1fr</div> </Row> </Stack> ) diff --git a/packages/ui/src/components/Stack/__stories__/DivWithBackground.css.ts b/packages/ui/src/components/Row/__stories__/DivWithBackground.css.ts similarity index 75% rename from packages/ui/src/components/Stack/__stories__/DivWithBackground.css.ts rename to packages/ui/src/components/Row/__stories__/DivWithBackground.css.ts index a902de43dd..88a279ca61 100644 --- a/packages/ui/src/components/Stack/__stories__/DivWithBackground.css.ts +++ b/packages/ui/src/components/Row/__stories__/DivWithBackground.css.ts @@ -1,7 +1,7 @@ -import { style } from '@vanilla-extract/css' import { theme } from '@ultraviolet/themes' +import { style } from '@vanilla-extract/css' -export const styledDiv = style({ +export const divWithBackground = style({ padding: theme.space[1], background: theme.colors.primary.background, color: theme.colors.primary.text, @@ -10,9 +10,4 @@ export const styledDiv = style({ display: 'flex', alignItems: 'center', justifyContent: 'center', - selectors: { - '&[data-width-full="true"]': { - width: '100%', - }, - }, }) diff --git a/packages/ui/src/components/Row/__stories__/DivWithBackground.ts b/packages/ui/src/components/Row/__stories__/DivWithBackground.ts deleted file mode 100644 index de75609063..0000000000 --- a/packages/ui/src/components/Row/__stories__/DivWithBackground.ts +++ /dev/null @@ -1,12 +0,0 @@ -import styled from '@emotion/styled' - -export const DivWithBackground = styled.div` - padding: ${({ theme }) => theme.space[1]}; - background: ${({ theme }) => theme.colors.primary.background}; - color: ${({ theme }) => theme.colors.primary.text}; - border-radius: ${({ theme }) => theme.radii.default}; - border: 1px solid ${({ theme }) => theme.colors.primary.border}; - display: flex; - align-items: center; - justify-content: center; -` diff --git a/packages/ui/src/components/Row/__stories__/Padding.stories.tsx b/packages/ui/src/components/Row/__stories__/Padding.stories.tsx index eb26ef84fb..ce3b6c038c 100644 --- a/packages/ui/src/components/Row/__stories__/Padding.stories.tsx +++ b/packages/ui/src/components/Row/__stories__/Padding.stories.tsx @@ -1,21 +1,21 @@ import type { StoryFn } from '@storybook/react-vite' import { Stack } from '../../Stack' import { Row } from '..' -import { DivWithBackground } from './DivWithBackground' +import { divWithBackground } from './DivWithBackground.css' export const Padding: StoryFn = args => ( <Stack> <Row {...args} padding="50px 0" templateColumns="1fr"> - <DivWithBackground>Padding-top and bottom: 50px</DivWithBackground> + <div className={divWithBackground}>Padding-top and bottom: 50px</div> </Row> <Row {...args} padding="0 50px" templateColumns="1fr"> - <DivWithBackground>Padding-left and right: 50px</DivWithBackground> + <div className={divWithBackground}>Padding-left and right: 50px</div> </Row> <Row {...args} padding="16px 32px 24px 8px" templateColumns="1fr"> - <DivWithBackground> + <div className={divWithBackground}> Padding-left: 8px, padding-right: 32px, padding-top: 16px, padding-bottom: 24px; - </DivWithBackground> + </div> </Row> </Stack> ) diff --git a/packages/ui/src/components/Row/__stories__/Responsive.stories.tsx b/packages/ui/src/components/Row/__stories__/Responsive.stories.tsx index 1d93981c38..2cb98243e8 100644 --- a/packages/ui/src/components/Row/__stories__/Responsive.stories.tsx +++ b/packages/ui/src/components/Row/__stories__/Responsive.stories.tsx @@ -3,7 +3,7 @@ import { consoleLightTheme } from '@ultraviolet/themes' import { useEffect, useState } from 'react' import { Stack } from '../../Stack' import { Row } from '..' -import { DivWithBackground } from './DivWithBackground' +import { divWithBackground } from './DivWithBackground.css' export const Responsive: StoryFn = props => { const [breakpoint, setBreakpoint] = useState<'xxsmall' | 'xsmall' | 'small'>( @@ -70,9 +70,13 @@ export const Responsive: StoryFn = props => { const sentiments = ['primary', 'success', 'danger'] return ( - <DivWithBackground data-sentiment={sentiments[idx]} key={idx}> + <div + className={divWithBackground} + data-sentiment={sentiments[idx]} + key={idx} + > {col} - </DivWithBackground> + </div> ) })} </Row> @@ -93,9 +97,13 @@ export const Responsive: StoryFn = props => { const sentiments = ['primary', 'success', 'danger'] return ( - <DivWithBackground data-sentiment={sentiments[idx]} key={idx}> + <div + className={divWithBackground} + data-sentiment={sentiments[idx]} + key={idx} + > {col} - </DivWithBackground> + </div> ) })} </Row> diff --git a/packages/ui/src/components/Row/__stories__/Template.stories.tsx b/packages/ui/src/components/Row/__stories__/Template.stories.tsx index b8b09fe7c8..cb6677ed3a 100644 --- a/packages/ui/src/components/Row/__stories__/Template.stories.tsx +++ b/packages/ui/src/components/Row/__stories__/Template.stories.tsx @@ -1,19 +1,31 @@ import type { StoryFn } from '@storybook/react-vite' import { Stack } from '../../Stack' import { Row } from '..' -import { DivWithBackground } from './DivWithBackground' +import { divWithBackground } from './DivWithBackground.css' export const Template: StoryFn<typeof Row> = ({ ...props }) => ( <Stack gap={1}> <Row gap={1} {...props} templateColumns="3fr 6fr 3fr"> - <DivWithBackground data-sentiment="primary">3fr</DivWithBackground> - <DivWithBackground data-sentiment="success">6fr</DivWithBackground> - <DivWithBackground data-sentiment="danger">3fr</DivWithBackground> + <div className={divWithBackground} data-sentiment="primary"> + 3fr + </div> + <div className={divWithBackground} data-sentiment="success"> + 6fr + </div> + <div className={divWithBackground} data-sentiment="danger"> + 3fr + </div> </Row> <Row gap={1} {...props} templateColumns="4fr 3fr 4fr"> - <DivWithBackground data-sentiment="primary">4fr</DivWithBackground> - <DivWithBackground data-sentiment="success">3fr</DivWithBackground> - <DivWithBackground data-sentiment="danger">4fr</DivWithBackground> + <div className={divWithBackground} data-sentiment="primary"> + 4fr + </div> + <div className={divWithBackground} data-sentiment="success"> + 3fr + </div> + <div className={divWithBackground} data-sentiment="danger"> + 4fr + </div> </Row> </Stack> ) diff --git a/packages/ui/src/components/Stack/__stories__/AlignItems.stories.tsx b/packages/ui/src/components/Stack/__stories__/AlignItems.stories.tsx index e731c9fd5a..5e23d5b5d4 100644 --- a/packages/ui/src/components/Stack/__stories__/AlignItems.stories.tsx +++ b/packages/ui/src/components/Stack/__stories__/AlignItems.stories.tsx @@ -1,4 +1,3 @@ -import { coloredChildren } from './helper' import { Template } from './Template.stories' export const AlignItems = Template.bind({}) @@ -14,6 +13,5 @@ AlignItems.parameters = { AlignItems.args = { alignItems: 'center', - children: coloredChildren, gap: 2, } diff --git a/packages/ui/src/components/Stack/__stories__/Direction.stories.tsx b/packages/ui/src/components/Stack/__stories__/Direction.stories.tsx index 7a70000a13..86097ad651 100644 --- a/packages/ui/src/components/Stack/__stories__/Direction.stories.tsx +++ b/packages/ui/src/components/Stack/__stories__/Direction.stories.tsx @@ -1,7 +1,7 @@ import type { StoryFn } from '@storybook/react-vite' import { Stack } from '../../Stack' import { Text } from '../../Text' -import { styledDiv } from './DivWithBackground.css' +import { child } from './styles.css' export const Direction: StoryFn = props => ( <Stack gap={3}> @@ -10,13 +10,13 @@ export const Direction: StoryFn = props => ( Direction Row: </Text> <Stack {...props} direction="row" gap={1}> - <div className={styledDiv} data-width-full> + <div className={child} data-width-full> First child </div> - <div className={styledDiv} data-width-full> + <div className={child} data-width-full> Second child </div> - <div className={styledDiv} data-width-full> + <div className={child} data-width-full> Third child </div> </Stack> @@ -26,9 +26,9 @@ export const Direction: StoryFn = props => ( Direction Column: </Text> <Stack {...props} direction="column" gap={1}> - <div className={styledDiv}>First child</div> - <div className={styledDiv}>Second child</div> - <div className={styledDiv}>Third child</div> + <div className={child}>First child</div> + <div className={child}>Second child</div> + <div className={child}>Third child</div> </Stack> </Stack> </Stack> diff --git a/packages/ui/src/components/Stack/__stories__/Responsive.stories.tsx b/packages/ui/src/components/Stack/__stories__/Responsive.stories.tsx index c24ca7b6c5..46190586c3 100644 --- a/packages/ui/src/components/Stack/__stories__/Responsive.stories.tsx +++ b/packages/ui/src/components/Stack/__stories__/Responsive.stories.tsx @@ -1,6 +1,6 @@ import type { StoryFn } from '@storybook/react-vite' import { Stack } from '../../Stack' -import { styledDiv } from './DivWithBackground.css' +import { child } from './styles.css' export const Responsive: StoryFn = props => ( <Stack @@ -8,13 +8,13 @@ export const Responsive: StoryFn = props => ( direction={{ small: 'row', xsmall: 'row', xxsmall: 'column' }} gap={{ small: 3, xsmall: 2, xxsmall: 1 }} > - <div className={styledDiv} data-width-full> + <div className={child} data-width-full> First child </div> - <div className={styledDiv} data-width-full> + <div className={child} data-width-full> Second child </div> - <div className={styledDiv} data-width-full> + <div className={child} data-width-full> Third child </div> </Stack> diff --git a/packages/ui/src/components/Stack/__stories__/Template.stories.tsx b/packages/ui/src/components/Stack/__stories__/Template.stories.tsx index 8df0f5173b..2319f2ba41 100644 --- a/packages/ui/src/components/Stack/__stories__/Template.stories.tsx +++ b/packages/ui/src/components/Stack/__stories__/Template.stories.tsx @@ -1,11 +1,11 @@ import type { StoryFn } from '@storybook/react-vite' import { Stack } from '..' -import { styledDiv } from './DivWithBackground.css' +import { child, firstChild, secondChild, thirdChild } from './styles.css' export const Template: StoryFn<typeof Stack> = props => ( <Stack {...props}> - <div className={styledDiv}>First child</div> - <div className={styledDiv}>Second child</div> - <div className={styledDiv}>Third child</div> + <div className={`${child} ${firstChild}`}>First child</div> + <div className={`${child} ${secondChild}`}>Second child</div> + <div className={`${child} ${thirdChild}`}>Third child</div> </Stack> ) diff --git a/packages/ui/src/components/Stack/__stories__/helper.tsx b/packages/ui/src/components/Stack/__stories__/helper.tsx index d3cdd4e092..452e07a418 100644 --- a/packages/ui/src/components/Stack/__stories__/helper.tsx +++ b/packages/ui/src/components/Stack/__stories__/helper.tsx @@ -1,30 +1,13 @@ -import styled from '@emotion/styled' - -const Child = styled.div` - display: flex; - align-items: center; - justify-content: center; - padding: ${({ theme }) => theme.space['2']}; - border-radius: ${({ theme }) => theme.radii.default}; -` - -const FirstChild = styled(Child)` - background-color: ${({ theme }) => theme.colors.info.background}; - color: ${({ theme }) => theme.colors.info.text}; -` - -const SecondChild = styled(Child)` - background-color: ${({ theme }) => theme.colors.primary.background}; - color: ${({ theme }) => theme.colors.primary.text}; -` - -const ThirdChild = styled(Child)` - background-color: ${({ theme }) => theme.colors.warning.background}; - color: ${({ theme }) => theme.colors.warning.text}; -` +import { child, firstChild, secondChild, thirdChild } from './styles.css' export const coloredChildren = [ - <FirstChild key="1">1</FirstChild>, - <SecondChild key="2">2</SecondChild>, - <ThirdChild key="3">3</ThirdChild>, + <div className={`${child} ${firstChild}`} key="1"> + 1 + </div>, + <div className={`${child} ${secondChild}`} key="2"> + 2 + </div>, + <div className={`${child} ${thirdChild}`} key="3"> + 3 + </div>, ] diff --git a/packages/ui/src/components/Stack/__stories__/styles.css.ts b/packages/ui/src/components/Stack/__stories__/styles.css.ts new file mode 100644 index 0000000000..11576387de --- /dev/null +++ b/packages/ui/src/components/Stack/__stories__/styles.css.ts @@ -0,0 +1,36 @@ +import { theme } from '@ultraviolet/themes' +import { style } from '@vanilla-extract/css' + +export const child = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: theme.space[1], + borderRadius: theme.radii.default, + color: theme.colors.primary.text, + border: `1px solid ${theme.colors.primary.border}`, + backgroundColor: theme.colors.primary.background, + selectors: { + '&[data-width-full="true"]': { + width: '100%', + }, + }, +}) + +export const firstChild = style({ + backgroundColor: theme.colors.info.background, + color: theme.colors.info.text, + border: `1px solid ${theme.colors.info.border}`, +}) + +export const secondChild = style({ + backgroundColor: theme.colors.primary.background, + color: theme.colors.primary.text, + border: `1px solid ${theme.colors.primary.border}`, +}) + +export const thirdChild = style({ + backgroundColor: theme.colors.warning.background, + color: theme.colors.warning.text, + border: `1px solid ${theme.colors.warning.border}`, +}) diff --git a/packages/ui/src/components/Text/__stories__/OneLine.stories.tsx b/packages/ui/src/components/Text/__stories__/OneLine.stories.tsx index 8881ac761f..5fe1a88477 100644 --- a/packages/ui/src/components/Text/__stories__/OneLine.stories.tsx +++ b/packages/ui/src/components/Text/__stories__/OneLine.stories.tsx @@ -1,33 +1,25 @@ -import styled from '@emotion/styled' import type { StoryFn } from '@storybook/react-vite' import { Text } from '../index' - -const Container = styled.div` - margin-bottom: ${({ theme }) => theme.space['2']}; - margin-top: ${({ theme }) => theme.space['1']}; - width: 200px; - background: ${({ theme }) => theme.colors.info.background}; - padding: ${({ theme }) => theme.space['1']}; -` +import { oneLineContainer } from './style.css' export const OneLine: StoryFn<typeof Text> = args => ( <> <strong>Without ellipsis</strong> - <Container> + <div className={oneLineContainer}> <Text {...args} as="div" variant="body"> This text is quite long. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </Text> - </Container> + </div> <strong>With ellipsis (a tooltip is displayed on hover)</strong> - <Container> + <div className={oneLineContainer}> <Text {...args} as="div" oneLine variant="body"> This text is quite long. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </Text> - </Container> + </div> </> ) diff --git a/packages/ui/src/components/Text/__stories__/style.css.ts b/packages/ui/src/components/Text/__stories__/style.css.ts new file mode 100644 index 0000000000..db90261c5f --- /dev/null +++ b/packages/ui/src/components/Text/__stories__/style.css.ts @@ -0,0 +1,10 @@ +import { theme } from '@ultraviolet/themes' +import { style } from '@vanilla-extract/css' + +export const oneLineContainer = style({ + marginBottom: theme.space[2], + marginTop: theme.space[2], + width: 200, + background: theme.colors.info.background, + padding: theme.space[1], +}) diff --git a/packages/ui/src/emotion.d.ts b/packages/ui/src/emotion.d.ts deleted file mode 100644 index fd16571f1d..0000000000 --- a/packages/ui/src/emotion.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { consoleLightTheme } from '@ultraviolet/themes' - -type UltravioletUITheme = typeof consoleLightTheme -declare module '@emotion/react' { - export interface Theme extends UltravioletUITheme {} -} diff --git a/packages/ui/src/helpers/__tests__/treeMap.test.ts b/packages/ui/src/helpers/__tests__/treeMap.test.ts index 96b74d9ffa..0768251ae7 100644 --- a/packages/ui/src/helpers/__tests__/treeMap.test.ts +++ b/packages/ui/src/helpers/__tests__/treeMap.test.ts @@ -1,8 +1,10 @@ -import { Theme } from '@emotion/react' import { DefaultTreeMapDatum } from '@nivo/treemap' +import { consoleLightTheme } from '@ultraviolet/themes' import { describe, expect, it } from 'vitest' import { getAllIds, getDataColors, getOpacity, percentToHex } from '../treeMap' +type Theme = typeof consoleLightTheme + describe('getOpacity', () => { it('should return 100 for input 0', () => { expect(getOpacity(0)).toBe(100) diff --git a/packages/ui/src/helpers/treeMap.ts b/packages/ui/src/helpers/treeMap.ts index 76e27224f1..e054453047 100644 --- a/packages/ui/src/helpers/treeMap.ts +++ b/packages/ui/src/helpers/treeMap.ts @@ -1,6 +1,7 @@ -import type { Theme } from '@emotion/react' import type { DefaultTreeMapDatum } from '@nivo/treemap' +import type { consoleLightTheme } from '@ultraviolet/themes' +type Theme = typeof consoleLightTheme type ReturnType = Record<string, string> /** diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index f3edfcac80..ed6a171b81 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -11,7 +11,6 @@ export { useTheme, } from './theme' export { - Breakpoint, bounce, bounceDefault, down, diff --git a/packages/ui/src/utils/index.ts b/packages/ui/src/utils/index.ts index f71d0c3707..6503e3e9d3 100644 --- a/packages/ui/src/utils/index.ts +++ b/packages/ui/src/utils/index.ts @@ -60,4 +60,4 @@ export { getUUID } from './ids' export { default as normalize } from './normalize' export type { ComparableType } from './orderBy' export { orderBy } from './orderBy' -export { Breakpoint, down, up } from './responsive' +export { down, up } from './responsive' diff --git a/packages/ui/src/utils/responsive/Breakpoint.tsx b/packages/ui/src/utils/responsive/Breakpoint.tsx deleted file mode 100644 index 00f5c8a67c..0000000000 --- a/packages/ui/src/utils/responsive/Breakpoint.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import styled from '@emotion/styled' -import type { ScreenSize } from '../../theme' -import { down, up } from './utilities' - -const Breakpoint = styled.div<{ down?: ScreenSize; up?: ScreenSize }>` - display: none; - ${props => ` - ${props.up ? up(props.up, 'display: block;') : ''} - ${props.down ? down(props.down, 'display: block;') : ''} - `} -` - -export default Breakpoint diff --git a/packages/ui/src/utils/responsive/index.ts b/packages/ui/src/utils/responsive/index.ts index 2361872e5c..91cafe82cc 100644 --- a/packages/ui/src/utils/responsive/index.ts +++ b/packages/ui/src/utils/responsive/index.ts @@ -1,2 +1 @@ -export { default as Breakpoint } from './Breakpoint' export { down, up } from './utilities' diff --git a/packages/ui/src/utils/responsive/style.css.ts b/packages/ui/src/utils/responsive/style.css.ts new file mode 100644 index 0000000000..88ccc73569 --- /dev/null +++ b/packages/ui/src/utils/responsive/style.css.ts @@ -0,0 +1,8 @@ +import { style } from '@vanilla-extract/css' + +export const breakpoint = style({ + display: 'none', +}) + +export const upStyle = style({}) +export const downStyle = style({}) diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json index 469acdba3f..cb80a12a22 100644 --- a/packages/ui/tsconfig.json +++ b/packages/ui/tsconfig.json @@ -3,6 +3,6 @@ "compilerOptions": { "baseUrl": "." }, - "include": ["src", "../../global.d.ts", "emotion.d.ts", "vitest.setup.ts"], + "include": ["src", "../../global.d.ts", "vitest.setup.ts"], "exclude": ["node_modules", "coverage", "dist"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f779289f0..b580da36f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,24 +47,6 @@ importers: '@commitlint/config-conventional': specifier: 20.0.0 version: 20.0.0 - '@emotion/babel-plugin': - specifier: 11.13.5 - version: 11.13.5 - '@emotion/cache': - specifier: 11.14.0 - version: 11.14.0 - '@emotion/eslint-plugin': - specifier: 11.12.0 - version: 11.12.0(eslint@9.39.1(jiti@2.4.2))(typescript@5.9.3) - '@emotion/jest': - specifier: 11.13.0 - version: 11.13.0 - '@emotion/react': - specifier: 11.14.0 - version: 11.14.0(@types/react@19.2.2)(react@19.2.0) - '@emotion/styled': - specifier: 11.14.1 - version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) '@eslint/compat': specifier: 1.4.1 version: 1.4.1(eslint@9.39.1(jiti@2.4.2)) @@ -146,6 +128,9 @@ importers: '@ultraviolet/ui': specifier: workspace:* version: link:packages/ui + '@vanilla-extract/css': + specifier: 1.17.4 + version: 1.17.4(babel-plugin-macros@3.1.0) '@vanilla-extract/vite-plugin': specifier: 5.1.1 version: 5.1.1(@types/node@22.18.11)(babel-plugin-macros@3.1.0)(jiti@2.4.2)(sass@1.94.0)(terser@5.39.0)(tsx@4.20.6)(vite@7.2.2(@types/node@22.18.11)(jiti@2.4.2)(sass@1.94.0)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) @@ -712,9 +697,6 @@ importers: packages/ui: dependencies: - '@emotion/serialize': - specifier: 1.3.3 - version: 1.3.3 '@nivo/bar': specifier: 0.89.1 version: 0.89.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -776,12 +758,6 @@ importers: '@babel/core': specifier: 7.28.5 version: 7.28.5 - '@emotion/react': - specifier: 11.14.0 - version: 11.14.0(@types/react@19.2.2)(react@19.2.0) - '@emotion/styled': - specifier: 11.14.1 - version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0) '@types/react': specifier: 19.2.2 version: 19.2.2 @@ -914,10 +890,6 @@ packages: resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} @@ -1431,14 +1403,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.26.9': - resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==} - engines: {node: '>=6.9.0'} - - '@babel/runtime@7.27.6': - resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.2': resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} engines: {node: '>=6.9.0'} @@ -1451,10 +1415,6 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.26.9': - resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.4': resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} engines: {node: '>=6.9.0'} @@ -1463,10 +1423,6 @@ packages: resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.9': - resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} - engines: {node: '>=6.9.0'} - '@babel/types@7.28.0': resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} engines: {node: '>=6.9.0'} @@ -1826,15 +1782,6 @@ packages: '@emnapi/wasi-threads@1.0.2': resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} - '@emotion/babel-plugin@11.13.5': - resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} - - '@emotion/cache@11.14.0': - resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} - - '@emotion/css-prettifier@1.2.0': - resolution: {integrity: sha512-p+9m/5fp61i90CGUT+516glGBXWoEHgSelybqR+5vlX6Kb+Z0rkOfEMFqTBwYMRxXZTitibZERl32n2yPma7Dw==} - '@emotion/eslint-plugin@11.12.0': resolution: {integrity: sha512-N0rtAVKk6w8RchWtexdG/GFbg48tdlO4cnq9Jg6H3ul3EDDgkYkPE0PKMb1/CJ7cDyYsiNPYVc3ZnWnd2/d0tA==} engines: {node: '>=6'} @@ -1844,62 +1791,6 @@ packages: '@emotion/hash@0.9.2': resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - '@emotion/is-prop-valid@1.3.1': - resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} - - '@emotion/jest@11.13.0': - resolution: {integrity: sha512-XyoUbJ9fthKdlXjTvjzd6aQ8yVWe68InZawFdGTFkJQRW44rsLHK1qjKB/+L7RiGgdm0BYFv7+tz8znQzRQOBw==} - peerDependencies: - '@types/jest': ^26.0.14 || ^27.0.0 || ^28.0.0 || ^29.0.0 - enzyme-to-json: ^3.2.1 - peerDependenciesMeta: - '@types/jest': - optional: true - enzyme-to-json: - optional: true - - '@emotion/memoize@0.9.0': - resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} - - '@emotion/react@11.14.0': - resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} - peerDependencies: - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - - '@emotion/serialize@1.3.3': - resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} - - '@emotion/sheet@1.4.0': - resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} - - '@emotion/styled@11.14.1': - resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==} - peerDependencies: - '@emotion/react': ^11.0.0-rc.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - - '@emotion/unitless@0.10.0': - resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0': - resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} - peerDependencies: - react: '>=16.8.0' - - '@emotion/utils@1.4.2': - resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} - - '@emotion/weak-memoize@0.4.0': - resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} - '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} @@ -4360,9 +4251,6 @@ packages: engines: {node: '>=16'} hasBin: true - convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -5413,10 +5301,6 @@ packages: resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} engines: {node: '>=0.10.0'} - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -5500,9 +5384,6 @@ packages: highlightjs-vue@1.0.0: resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} @@ -7513,10 +7394,6 @@ packages: source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -7539,10 +7416,6 @@ packages: spdx-license-ids@3.0.21: resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} - specificity@0.4.1: - resolution: {integrity: sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==} - hasBin: true - split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -7705,9 +7578,6 @@ packages: peerDependencies: postcss: ^8.2.15 - stylis@4.2.0: - resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} - supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -8539,13 +8409,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.25.9': - dependencies: - '@babel/traverse': 7.26.9 - '@babel/types': 7.26.9 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-imports@7.27.1': dependencies: '@babel/traverse': 7.28.4 @@ -9200,12 +9063,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/runtime@7.26.9': - dependencies: - regenerator-runtime: 0.14.1 - - '@babel/runtime@7.27.6': {} - '@babel/runtime@7.28.2': {} '@babel/runtime@7.28.4': {} @@ -9216,18 +9073,6 @@ snapshots: '@babel/parser': 7.28.5 '@babel/types': 7.28.5 - '@babel/traverse@7.26.9': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 - debug: 4.4.3 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - '@babel/traverse@7.28.4': dependencies: '@babel/code-frame': 7.27.1 @@ -9252,11 +9097,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/types@7.26.9': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/types@7.28.0': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -9928,35 +9768,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emotion/babel-plugin@11.13.5': - dependencies: - '@babel/helper-module-imports': 7.25.9 - '@babel/runtime': 7.26.9 - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/serialize': 1.3.3 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.2.0 - transitivePeerDependencies: - - supports-color - - '@emotion/cache@11.14.0': - dependencies: - '@emotion/memoize': 0.9.0 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - stylis: 4.2.0 - - '@emotion/css-prettifier@1.2.0': - dependencies: - '@emotion/memoize': 0.9.0 - stylis: 4.2.0 - '@emotion/eslint-plugin@11.12.0(eslint@9.39.1(jiti@2.4.2))(typescript@5.9.3)': dependencies: '@typescript-eslint/utils': 5.62.0(eslint@9.39.1(jiti@2.4.2))(typescript@5.9.3) @@ -9967,71 +9778,6 @@ snapshots: '@emotion/hash@0.9.2': {} - '@emotion/is-prop-valid@1.3.1': - dependencies: - '@emotion/memoize': 0.9.0 - - '@emotion/jest@11.13.0': - dependencies: - '@babel/runtime': 7.28.4 - '@emotion/css-prettifier': 1.2.0 - chalk: 4.1.2 - specificity: 0.4.1 - stylis: 4.2.0 - - '@emotion/memoize@0.9.0': {} - - '@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0)': - dependencies: - '@babel/runtime': 7.27.6 - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.0) - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - hoist-non-react-statics: 3.3.2 - react: 19.2.0 - optionalDependencies: - '@types/react': 19.2.2 - transitivePeerDependencies: - - supports-color - - '@emotion/serialize@1.3.3': - dependencies: - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/unitless': 0.10.0 - '@emotion/utils': 1.4.2 - csstype: 3.1.3 - - '@emotion/sheet@1.4.0': {} - - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@19.2.0))(@types/react@19.2.2)(react@19.2.0)': - dependencies: - '@babel/runtime': 7.27.6 - '@emotion/babel-plugin': 11.13.5 - '@emotion/is-prop-valid': 1.3.1 - '@emotion/react': 11.14.0(@types/react@19.2.2)(react@19.2.0) - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.0) - '@emotion/utils': 1.4.2 - react: 19.2.0 - optionalDependencies: - '@types/react': 19.2.2 - transitivePeerDependencies: - - supports-color - - '@emotion/unitless@0.10.0': {} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.0)': - dependencies: - react: 19.2.0 - - '@emotion/utils@1.4.2': {} - - '@emotion/weak-memoize@0.4.0': {} - '@epic-web/invariant@1.0.0': {} '@esbuild/aix-ppc64@0.25.0': @@ -11466,7 +11212,8 @@ snapshots: '@types/normalize-package-data@2.4.4': {} - '@types/parse-json@4.0.2': {} + '@types/parse-json@4.0.2': + optional: true '@types/react-dom@19.2.2(@types/react@19.2.2)': dependencies: @@ -12400,6 +12147,7 @@ snapshots: '@babel/runtime': 7.28.4 cosmiconfig: 7.1.0 resolve: 1.22.10 + optional: true babel-plugin-named-exports-order@0.0.2: {} @@ -12781,8 +12529,6 @@ snapshots: meow: 12.1.1 split2: 4.2.0 - convert-source-map@1.9.0: {} - convert-source-map@2.0.0: {} cookie@1.0.2: {} @@ -12811,6 +12557,7 @@ snapshots: parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 + optional: true cosmiconfig@8.3.6(typescript@5.9.3): dependencies: @@ -14090,8 +13837,6 @@ snapshots: is-windows: 1.0.2 which: 1.3.1 - globals@11.12.0: {} - globals@14.0.0: {} globals@16.4.0: {} @@ -14166,10 +13911,6 @@ snapshots: highlightjs-vue@1.0.0: {} - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.13.1 - homedir-polyfill@1.0.3: dependencies: parse-passwd: 1.0.0 @@ -16517,8 +16258,6 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 - source-map@0.5.7: {} - source-map@0.6.1: {} space-separated-tokens@1.1.5: {} @@ -16542,8 +16281,6 @@ snapshots: spdx-license-ids@3.0.21: {} - specificity@0.4.1: {} - split2@4.2.0: {} sprintf-js@1.0.3: {} @@ -16732,8 +16469,6 @@ snapshots: postcss: 8.5.6 postcss-selector-parser: 6.1.2 - stylis@4.2.0: {} - supports-color@5.5.0: dependencies: has-flag: 3.0.0 diff --git a/tsconfig.json b/tsconfig.json index c958a1dac5..5f5ed65cca 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,6 @@ "compilerOptions": { "target": "esnext", "module": "esnext", - "jsxImportSource": "@emotion/react", "types": ["vite/client", "@testing-library/jest-dom"], "noEmit": true, "skipLibCheck": true, diff --git a/utils/scripts/analyse-deps.ts b/utils/scripts/analyse-deps.ts index 0b10268ef3..406c45d94b 100644 --- a/utils/scripts/analyse-deps.ts +++ b/utils/scripts/analyse-deps.ts @@ -58,14 +58,9 @@ for (const file of filesToAnalyze) { .replace(/\.tsx?$/, '') if ( - ![ - 'react', - 'react-vite', - 'vitest', - 'styled', - '@emotion/styled', - 'components/', - ].some(string => normalizedFile.endsWith(string)) + !['react', 'react-vite', 'vitest', 'styled', 'components/'].some( + string => normalizedFile.endsWith(string), + ) ) { const importedComponent = normalizedFile.split('/').toReversed()[0] diff --git a/vite.config.ts b/vite.config.ts index 86d54f2b3a..7b9b26e9a1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,7 +9,6 @@ import type { ViteUserConfig } from 'vitest/config' const pkg = await readPackage() const externalPkgs = [ - '@emotion', ...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.optionalDependencies || {}), ...Object.keys(pkg.peerDependencies || {}), @@ -69,10 +68,6 @@ export const defaultConfig: ViteUserConfig = { }, plugins: [ react({ - babel: { - plugins: ['@emotion/babel-plugin'], - }, - jsxImportSource: '@emotion/react', jsxRuntime: 'automatic', }), preserveDirectives(),