diff --git a/.changeset/long-crews-join.md b/.changeset/long-crews-join.md new file mode 100644 index 0000000000..af732b5c71 --- /dev/null +++ b/.changeset/long-crews-join.md @@ -0,0 +1,7 @@ +--- +"@ultraviolet/ui": major +--- + +⚠️ Breaking changes: + +`ThemeRegistry` is no longer available in this package. You can import it by adding `@ultraviolet/nextjs` package to your project. The component is the same only the import changes. diff --git a/.changeset/true-ducks-deny.md b/.changeset/true-ducks-deny.md new file mode 100644 index 0000000000..68858a2807 --- /dev/null +++ b/.changeset/true-ducks-deny.md @@ -0,0 +1,5 @@ +--- +"@ultraviolet/nextjs": major +--- + +New package and component ThemeRegistry for next compatibility diff --git a/examples/next-app-router/app/layout.tsx b/examples/next-app-router/app/layout.tsx index c1477b7312..4b65336237 100644 --- a/examples/next-app-router/app/layout.tsx +++ b/examples/next-app-router/app/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next' import './globals.css' +import { ThemeRegistry } from '@ultraviolet/nextjs' import { consoleLightTheme } from '@ultraviolet/themes' -import { ThemeRegistry } from '@ultraviolet/ui' import { ReactNode } from 'react' import { GlobalStyles } from './GlobalStyles' diff --git a/examples/next-app-router/package.json b/examples/next-app-router/package.json index ccb4cbd0ec..7b7710a11e 100644 --- a/examples/next-app-router/package.json +++ b/examples/next-app-router/package.json @@ -15,6 +15,7 @@ "@ultraviolet/fonts": "workspace:*", "@ultraviolet/form": "workspace:*", "@ultraviolet/icons": "workspace:*", + "@ultraviolet/nextjs": "workspace:*", "@ultraviolet/ui": "workspace:*", "next": "15.3.4", "react": "19.1.0", diff --git a/packages/nextjs/.npmignore b/packages/nextjs/.npmignore new file mode 100644 index 0000000000..9091e82d19 --- /dev/null +++ b/packages/nextjs/.npmignore @@ -0,0 +1,3 @@ +/* +!/dist/**/*.js +*.test.js diff --git a/packages/nextjs/README.md b/packages/nextjs/README.md new file mode 100644 index 0000000000..de1ae03d64 --- /dev/null +++ b/packages/nextjs/README.md @@ -0,0 +1,78 @@ +# Ultraviolet NextJS + +[![npm version](https://badge.fury.io/js/%40ultraviolet%2Fnextjs.svg)](https://badge.fury.io/js/%40ultraviolet%2Fui) + +Ultraviolet JS is a utility package to make Ultraviolet UI work with NextJS. This package will guide you through the integration of Ultraviolet UI with Next.js [App Router](https://nextjs.org/docs/app). + +### Get Started + +```sh +pnpm add @ultraviolet/nextjs @ultraviolet/ui @ultraviolet/themes @emotion/react @emotion/styled @emotion/cache +``` + +In you NextJS project you can implement the following: + +```tsx +// app/layout.tsx +import { consoleLightTheme } from '@ultraviolet/themes' +import { ThemeRegistry } from '@ultraviolet/nextjs' +import { Button } from '@ultraviolet/ui' +import { ReactNode } from 'react' + +export default function RootLayout({ + children, +}: Readonly<{ + children: ReactNode +}>) { + return ( + + + + {children} + + + + + ) +} +``` + +### Limitations + +- **Fonts**: Ultraviolet UI uses custom fonts that need to be imported separately. Make sure to import the fonts CSS file in your project's entry point: +```sh +pnpm add @ultraviolet/fonts +``` + + then in you `GlobalStyle` file: + + ```tsx + "use client" + + import '@ultraviolet/fonts/fonts.css' + ``` + +- **Styled Components**: in order to customize an emotion component, you need to import the `styled` function from `@emotion/styled` and use it to create your styled components. Emotion is not yet compatible with server components, so you need to use the `styled` function in a client component. Example: + + ```tsx + "use client" + + import { styled } from '@emotion/styled' + + const Button = styled.button` + background-color: #0070f3; + color: white; + padding: 10px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + ` + ``` + +## Documentation + +Checkout our [documentation website](https://storybook.ultraviolet.scaleway.com/). + +## Contributing + +📝 You can participate in the development and [start contributing](/CONTRIBUTING.md) to it. diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json new file mode 100644 index 0000000000..e378b02838 --- /dev/null +++ b/packages/nextjs/package.json @@ -0,0 +1,71 @@ +{ + "name": "@ultraviolet/nextjs", + "version": "0.0.1", + "description": "Ultraviolet NextJS utility package", + "homepage": "https://github.com/scaleway/ultraviolet#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/scaleway/ultraviolet.git", + "directory": "packages/nextjs" + }, + "scripts": { + "type:generate": "tsc --declaration -p tsconfig.build.json", + "watch": "pnpm run '/^watch:.*/'", + "watch:build": "vite build --config vite.config.ts --watch", + "build": "vite build --config vite.config.ts && pnpm run type:generate", + "build:profile": "npx vite-bundle-visualizer -c vite.config.ts", + "typecheck": "tsc --noEmit", + "lintpublish": "publint" + }, + "keywords": [ + "react", + "reactjs", + "ui", + "nextjs" + ], + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "engines": { + "node": ">=18.x", + "pnpm": ">=9.x" + }, + "os": [ + "darwin", + "linux" + ], + "sideEffects": false, + "type": "module", + "files": [ + "dist/" + ], + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "main": "./dist/index.cjs", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.cjs", + "default": "./dist/index.js" + } + }, + "peerDependencies": { + "@emotion/cache": "11.14.0", + "@emotion/react": "11.14.0", + "@emotion/styled": "11.14.0", + "@ultraviolet/ui": "workspace:*", + "next": "15.x", + "react": "18.x || 19.x", + "react-dom": "18.x || 19.x" + }, + "devDependencies": { + "@emotion/cache": "11.14.0", + "@emotion/react": "11.14.0", + "@emotion/styled": "11.14.0", + "@ultraviolet/ui": "workspace:*", + "next": "15.3.4", + "react": "19.1.0", + "react-dom": "19.1.0" + } +} diff --git a/packages/ui/src/theme/ThemeRegistry.tsx b/packages/nextjs/src/ThemeRegistry.tsx similarity index 95% rename from packages/ui/src/theme/ThemeRegistry.tsx rename to packages/nextjs/src/ThemeRegistry.tsx index d7716df4ea..2adb8bd3a2 100644 --- a/packages/ui/src/theme/ThemeRegistry.tsx +++ b/packages/nextjs/src/ThemeRegistry.tsx @@ -15,7 +15,7 @@ type ThemeRegistryProps = { * ThemeRegistry is a component that provides a theme to its children. * This solution is provided to work with Next.js app router. */ -export default function ThemeRegistry({ children, theme }: ThemeRegistryProps) { +export const ThemeRegistry = ({ children, theme }: ThemeRegistryProps) => { const [{ cache, flush }] = useState(() => { const localCache = createCache({ key: 'uv' }) localCache.compat = true diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts new file mode 100644 index 0000000000..a8013ab758 --- /dev/null +++ b/packages/nextjs/src/index.ts @@ -0,0 +1 @@ +export { ThemeRegistry } from './ThemeRegistry' diff --git a/packages/nextjs/tsconfig.build.json b/packages/nextjs/tsconfig.build.json new file mode 100644 index 0000000000..128326c236 --- /dev/null +++ b/packages/nextjs/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "emitDeclarationOnly": true, + "rootDir": "src", + "outDir": "dist" + }, + "exclude": [ + "*.config.ts", + "*.setup.ts", + "**/__tests__", + "**/__mocks__", + "**/__stories__", + "src/**/*.test.tsx", + "vitest.setup.ts" + ] +} diff --git a/packages/nextjs/tsconfig.json b/packages/nextjs/tsconfig.json new file mode 100644 index 0000000000..05dc71e12e --- /dev/null +++ b/packages/nextjs/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "." + }, + "include": ["src", "../../global.d.ts"], + "exclude": ["node_modules", "coverage", "dist"] +} diff --git a/packages/nextjs/vite.config.ts b/packages/nextjs/vite.config.ts new file mode 100644 index 0000000000..181694793c --- /dev/null +++ b/packages/nextjs/vite.config.ts @@ -0,0 +1,4 @@ +import { defineConfig } from 'vite' +import { defaultConfig } from '../../vite.config' + +export default defineConfig(defaultConfig) diff --git a/packages/ui/package.json b/packages/ui/package.json index 431935b069..e63e07e1cd 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -82,7 +82,6 @@ "react-dom": "19.1.0" }, "dependencies": { - "@emotion/cache": "11.14.0", "@emotion/serialize": "1.3.3", "@nivo/bar": "0.89.1", "@nivo/core": "0.89.1", @@ -94,7 +93,6 @@ "@ultraviolet/icons": "workspace:*", "@ultraviolet/themes": "workspace:*", "deepmerge": "4.3.1", - "next": "15.3.4", "react-toastify": "11.0.5", "react-use-clipboard": "1.0.9" } diff --git a/packages/ui/src/__stories__/Guides/NextIntegration.mdx b/packages/ui/src/__stories__/Guides/NextIntegration.mdx index 2472827711..9024d68903 100644 --- a/packages/ui/src/__stories__/Guides/NextIntegration.mdx +++ b/packages/ui/src/__stories__/Guides/NextIntegration.mdx @@ -1,84 +1,8 @@ -import { Meta } from '@storybook/blocks' +import { Markdown, Meta } from '@storybook/blocks' +import Readme from '../../../../nextjs/README.md?raw' -# Next Integration - -Learn how to use Ultraviolet UI with Next.js. - -## App Router - -This section will guide you through the integration of Ultraviolet UI with Next.js [App Router](https://nextjs.org/docs/app). - -### Install dependencies - -Start by installing Ultraviolet dependencies and emotion dependencies. - -```sh -pnpm add @ultraviolet/ui @ultraviolet/themes @emotion/react @emotion/styled -``` - -### Configuration - -Inside `app/layout.tsx`, import the `ThemeRegistry` from `@ultraviolet/ui` and wrap your app with it. - -```tsx -// app/layout.tsx -import { consoleLightTheme } from '@ultraviolet/themes' -import { ThemeRegistry } from '@ultraviolet/ui' -import { ReactNode } from 'react' - -export default function RootLayout({ - children, -}: Readonly<{ - children: ReactNode -}>) { - return ( - - - - {children} - - - - ) -} -``` - -`ThemeRegistry` component is responsible for providing the theme context to all components within the app. -This component has been specially made to work seamlessly with Next.js's App Router. - -### Limitations - -- **Fonts**: Ultraviolet UI uses custom fonts that need to be imported separately. Make sure to import the fonts CSS file in your project's entry point: - ```sh - pnpm add @ultraviolet/fonts - ``` - - then in you `GlobalStyle` file: - ```tsx - "use client" - - import '@ultraviolet/fonts/fonts.css' - ``` - -- **Styled Components**: in order to customize an emotion component, you need to import the `styled` function from `@emotion/styled` and use it to create your styled components. Emotion is not yet compatible with server components, so you need to use the `styled` function in a client component. Example: - ```tsx - "use client" - - import { styled } from '@emotion/styled' - - const Button = styled.button` - background-color: #0070f3; - color: white; - padding: 10px 20px; - border: none; - border-radius: 4px; - cursor: pointer; - ` - ``` - -### Examples - -In order to help you integrate Ultraviolet UI with Next.js, we've prepared an example available on GitHub: -[https://github.com/scaleway/ultraviolet/tree/main/examples/next-app-router](https://github.com/scaleway/ultraviolet/tree/main/examples/next-app-router) + + {Readme} + diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index eda643386f..b09d9f0320 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -4,7 +4,6 @@ export { darkTheme, default as theme, extendTheme, - ThemeRegistry, } from './theme' export type { SCWUITheme, UltravioletUITheme } from './theme' export { diff --git a/packages/ui/src/theme/index.ts b/packages/ui/src/theme/index.ts index bf3c40a35c..2321322415 100644 --- a/packages/ui/src/theme/index.ts +++ b/packages/ui/src/theme/index.ts @@ -1,6 +1,5 @@ import { consoleDarkTheme, consoleLightTheme } from '@ultraviolet/themes' import deepmerge from 'deepmerge' -import ThemeRegistry from './ThemeRegistry' export type ScreenSize = keyof typeof consoleLightTheme.breakpoints @@ -66,7 +65,6 @@ export { SENTIMENTS, SENTIMENTS_WITHOUT_NEUTRAL, typography, - ThemeRegistry, } export default consoleLightTheme diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac1074735c..13ad60ddfb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -475,6 +475,9 @@ importers: '@ultraviolet/icons': specifier: workspace:* version: link:../../packages/icons + '@ultraviolet/nextjs': + specifier: workspace:* + version: link:../../packages/nextjs '@ultraviolet/ui': specifier: workspace:* version: link:../../packages/ui @@ -663,6 +666,30 @@ importers: specifier: 19.1.0 version: 19.1.0(react@19.1.0) + packages/nextjs: + devDependencies: + '@emotion/cache': + specifier: 11.14.0 + version: 11.14.0 + '@emotion/react': + specifier: 11.14.0 + version: 11.14.0(@types/react@19.1.8)(react@19.1.0) + '@emotion/styled': + specifier: 11.14.0 + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@19.1.0))(@types/react@19.1.8)(react@19.1.0) + '@ultraviolet/ui': + specifier: workspace:* + version: link:../ui + next: + specifier: 15.3.4 + version: 15.3.4(@babel/core@7.27.4)(@playwright/test@1.53.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: + specifier: 19.1.0 + version: 19.1.0 + react-dom: + specifier: 19.1.0 + version: 19.1.0(react@19.1.0) + packages/plus: dependencies: '@uiw/codemirror-extensions-langs': @@ -719,9 +746,6 @@ importers: packages/ui: dependencies: - '@emotion/cache': - specifier: 11.14.0 - version: 11.14.0 '@emotion/serialize': specifier: 1.3.3 version: 1.3.3 @@ -755,9 +779,6 @@ importers: deepmerge: specifier: 4.3.1 version: 4.3.1 - next: - specifier: 15.3.4 - version: 15.3.4(@babel/core@7.27.4)(@playwright/test@1.53.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-toastify: specifier: 11.0.5 version: 11.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -13076,8 +13097,8 @@ snapshots: '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.29.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.8.7(eslint-plugin-import@2.31.0)(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.8.7)(eslint@9.29.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.8.7(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.8.7(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.4.2)) eslint-plugin-react: 7.37.4(eslint@9.29.0(jiti@2.4.2)) eslint-plugin-react-hooks: 5.2.0(eslint@9.29.0(jiti@2.4.2)) @@ -13100,6 +13121,21 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-import-resolver-typescript@3.8.7(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.1 + enhanced-resolve: 5.18.1 + eslint: 9.29.0(jiti@2.4.2) + get-tsconfig: 4.10.0 + is-bun-module: 1.3.0 + stable-hash: 0.0.4 + tinyglobby: 0.2.13 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.8.7(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + eslint-import-resolver-typescript@3.8.7(eslint-plugin-import@2.31.0)(eslint@9.29.0(jiti@2.4.2)): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -13111,7 +13147,7 @@ snapshots: stable-hash: 0.0.4 tinyglobby: 0.2.13 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.8.7)(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.26.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.8.7)(eslint@9.29.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -13126,14 +13162,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.7)(eslint@9.29.0(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.7(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.29.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.8.7(eslint-plugin-import@2.31.0)(eslint@9.29.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.8.7(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -13172,7 +13208,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.8.7)(eslint@9.29.0(jiti@2.4.2)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.8.7(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -13183,7 +13219,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.29.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.7)(eslint@9.29.0(jiti@2.4.2)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.8.7(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3