Skip to content

Commit 435becb

Browse files
authored
feat(ui): add ui-components package (#7401)
* feat(ui): add ui-components package * fixup! feat(ui): add ui-components package * fixup! fixup! feat(ui): add ui-components package * allow manual deployment of linting * Fix indentation in COLLABORATOR_GUIDE.md * resolve reviews * undo allowing manual deployment
1 parent 8eba3da commit 435becb

File tree

266 files changed

+2884
-2014
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

266 files changed

+2884
-2014
lines changed

.github/workflows/lint-and-tests.yml

+7-4
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ jobs:
204204
# sha reference has no stable git tag reference or URL. see https://github.com/chromaui/chromatic-cli/issues/797
205205
uses: chromaui/action@30b6228aa809059d46219e0f556752e8672a7e26
206206
with:
207-
workingDir: apps/site
207+
workingDir: packages/ui-components
208208
buildScriptName: storybook:build
209209
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
210210
exitOnceUploaded: true
@@ -220,6 +220,9 @@ jobs:
220220
uses: MishaKav/jest-coverage-comment@d74238813c33e6ea20530ff91b5ea37953d11c91 # v1.0.27
221221
with:
222222
title: 'Unit Test Coverage Report'
223-
junitxml-path: ./apps/site/junit.xml
224-
junitxml-title: Unit Test Report
225-
coverage-summary-path: ./apps/site/coverage/coverage-summary.json
223+
multiple-junitxml-files: |
224+
@node-core/ui-components, ./packages/ui-components/junit.xml
225+
@nodejs/website, ./apps/site/junit.xml
226+
multiple-files: |
227+
@node-core/ui-components, ./packages/ui-components/coverage/coverage-summary.json
228+
@nodejs/website, ./apps/site/coverage/coverage-summary.json

COLLABORATOR_GUIDE.md

+75-27
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,10 @@ The Website also uses several other Open Source libraries (not limited to) liste
102102
Locations are subject to change. (If you are someone updating these paths,
103103
please document those changes here.)
104104

105-
- React Components are defined on `apps/site/components`
105+
- React Components are defined on `apps/site/components` and `packages/ui-components`
106106
- React Templates are defined on `apps/site/layouts`
107-
- Global Stylesheets are declared on `apps/site/styles`
108-
- Styles are done with [PostCSS][]
107+
- Global Stylesheets are declared on `packages/ui-components/styles`
108+
- Styles are done with [PostCSS][] and [Tailwind][]
109109
- Public files are stored on `apps/site/public`
110110
- Static Images, JavaScript files, and others are stored within `apps/site/public/static`
111111
- Internationalisation is done on `apps/site/i18n`
@@ -126,7 +126,7 @@ please document those changes here.)
126126
- Generation of build-time indexes such as blog data
127127
- Multi-Purpose Scripts are stored within `apps/site/scripts`
128128
- Such as Node.js Release Blog Post generation
129-
- Storybook Configuration is done within `apps/site/.storybook`
129+
- Storybook Configuration is done within `packages/ui-components/.storybook`
130130
- We use an almost out-of-the-box Storybook Experience with a few extra customisations
131131

132132
### Adding new Pages
@@ -197,8 +197,6 @@ Finally, if you're unfamiliar with how to use Tailwind or how to use Tailwind wi
197197
- We discourage the usage of any plain CSS styles and tokens, when in doubt ask for help
198198
- We require that you define one Tailwind Token per line, just as shown on the example above, since this improves readability
199199
- Only write CSS within CSS Modules, avoid writing CSS within JavaScript files
200-
- We recommend creating mixins for reusable animations, effects and more
201-
- You can create Mixins within the `apps/site/styles/mixins` folder
202200

203201
> \[!NOTE]\
204202
> Tailwind is already configured for this repository. You don't need to import any Tailwind module within your CSS module.\
@@ -211,26 +209,76 @@ Finally, if you're unfamiliar with how to use Tailwind or how to use Tailwind wi
211209
212210
### Best practices when creating a Component
213211

214-
- All React Components should be placed within the `apps/site/components` folder.
215-
- Each Component should be placed, whenever possible, within a sub-folder, which we call the "Domain" of the Component
216-
- The domain represents where these Components belong to or where they will be used.
217-
- For example, Components used within Article Pages or that are part of the structure of an Article or the Article Layouts,
218-
should be placed within `apps/site/components/Article`
219-
- Each component should have its folder with the name of the Component
220-
- The structure of each component folder follows the following template:
212+
- **All React components** should be placed within either `@node-core/ui-components` (for reusable components) or `apps/site/components` (for website-specific components).
213+
- **Generic UI components** that are not tied to the website should be placed in the `@node-core/ui-components` package.
214+
- These components should be **framework-agnostic** and must not rely on Next.js-specific features such as `usePathname()` or `useTranslations()`.
215+
- If a component previously relied on Next.js, it should now accept these values as **props** instead.
216+
- **Website-specific components** that rely on Next.js or are tied to the website should remain in `apps/site/components`.
217+
- These components can use Next.js-specific hooks, API calls, or configurations.
218+
- When using a generic UI component that requires Next.js functionality, pass it as a **prop** instead of modifying the component.
219+
- **Each component** should be placed within a sub-folder, which we call the **"Domain"** of the component.
220+
- The domain represents where the component belongs or where it will be used.
221+
- For example, components used within article pages or related to the structure of an article should be placed within `@node-core/ui-components/Common/Article`.
222+
- **Each component should have its own folder** with the name of the component.
223+
- The structure of each component folder follows this template:
224+
221225
```text
222226
- ComponentName
223-
- index.tsx // the component itself
224-
- index.module.css // all styles of the component are placed there
225-
- index.stories.tsx // component Storybook stories
226-
- __tests__ // component tests (such as unit tests, etc)
227-
- index.test.mjs // unit tests should be done in ESM and not TypeScript
227+
- index.tsx // The component itself
228+
- index.module.css // Component-specific styles
229+
- index.stories.tsx // Storybook stories (only for @node-core/ui-components)
230+
- __tests__/ // Component tests (such as unit tests, etc.)
231+
- index.test.mjs // Unit tests should be done in ESM, not TypeScript
228232
```
229-
- React Hooks belonging to a single Component should be placed within the Component's folder
230-
- If the Hook as a wider usability or can be used by other components, it should be placed in the root `hooks` folder.
231-
- If the Component has "sub-components" they should follow the same philosophy as the Component itself.
232-
- For example, if the Component `ComponentName` has a sub-component called `SubComponentName`,
233-
then it should be placed within `ComponentName/SubComponentName`
233+
234+
- **If a component requires Next.js features, it should be wrapped within `apps/site`** rather than being modified directly in `@node-core/ui-components`.
235+
236+
- Example: A component that requires `usePathname()` should **not** call it directly inside `@node-core/ui-components`. Instead:
237+
- The **base component** should accept `pathname` as a prop.
238+
- The **wrapper component** in `apps/site` should call `usePathname()` and pass it to the base component.
239+
240+
Example structure:
241+
242+
- **Base Component (`@node-core/ui-components`)**
243+
244+
```tsx
245+
const BaseComponent: FC<...> = ({ pathname, ariaLabel }) => {
246+
return <... ariaLabel={ariaLabel}></...>;
247+
};
248+
```
249+
250+
- **Wrapper Component (`apps/site/components`)**
251+
252+
```tsx
253+
const Component: FC<...> = (...) => {
254+
const pathname = usePathname();
255+
const t = useTranslations();
256+
257+
return <BaseComponent pathname={pathname} ariaLabel={t('my.key')} />;
258+
};
259+
```
260+
261+
- **Importing Components:**
262+
- **For website-specific functionality**, import the wrapper from `apps/site/components`.
263+
- **For direct UI use cases**, import from `@node-core/ui-components`.
264+
265+
- **Storybook is now a dependency of `@node-core/ui-components`** and should not be included in `apps/site`.
266+
267+
- Storybook stories should be written only for components in `@node-core/ui-components`.
268+
269+
- **React Hooks that belong to a single component should be placed within that components folder.**
270+
271+
- If the hook has a **wider usability** or can be used by multiple components, it should be placed in the `apps/site/hooks` folder.
272+
- These hooks should only exist in `apps/site`.
273+
274+
- **If a component has sub-components, they should follow the same structure as the main component.**
275+
- Example: If `ComponentName` has a sub-component called `SubComponentName`, it should be placed within:
276+
```text
277+
- ComponentName/
278+
- index.tsx
279+
- SubComponentName/
280+
- index.tsx
281+
```
234282

235283
#### How a new Component should look like when freshly created
236284

@@ -272,7 +320,7 @@ To add a new download installation method, follow these steps:
272320
273321
- Add a new entry to the `INSTALL_METHODS` array.
274322
- Each entry should have the following properties:
275-
- `iconImage`: The React component of the icon image for the installation method. This should be an SVG component stored within `apps/site/components/Icons/InstallationMethod` and must follow the other icon component references (being a `FC` supporting `SVGSVGElement` props).
323+
- `iconImage`: The React component of the icon image for the installation method. This should be an SVG component stored within `@node-core/ui-components/Icons/InstallationMethod` and must follow the other icon component references (being a `FC` supporting `SVGSVGElement` props).
276324
- Don't forget to add it on the `index.tsx` file from the `InstallationMethod` folder.
277325
- `recommended`: A boolean indicating if this method is recommended. This property is available only for official installation methods.
278326
- `url`: The URL for the installation method.
@@ -379,7 +427,7 @@ Each new feature or bug fix should be accompanied by a unit test (when deemed va
379427
We use [Jest][] as our test runner and [React Testing Library][] for our React unit tests.
380428

381429
We also use [Storybook][] to document our components.
382-
Each component should have a storybook story that documents the component's usage.
430+
Components within `packages/ui-components` should have a storybook story that documents the component's usage.
383431

384432
Visual Regression Testing is automatically done via [Chromatic](https://www.chromatic.com/) to ensure that Components are rendered correctly.
385433

@@ -407,7 +455,7 @@ They also allow Developers to preview Components and be able to test them manual
407455

408456
```tsx
409457
import type { Meta as MetaObj, StoryObj } from '@storybook/react';
410-
import NameOfComponent from '@components/PathTo/YourComponent';
458+
import NameOfComponent from '@node-core/ui-components/PathTo/YourComponent';
411459

412460
type Story = StoryObj<typeof NameOfComponent>;
413461
type Meta = MetaObj<typeof NameOfComponent>;
@@ -548,7 +596,7 @@ The Node.js Website uses Tailwind as a CSS Framework for crafting our React Comp
548596
#### Font Families on the Website
549597

550598
We use `next/fonts` Open Sans as the default font for the Node.js Website.
551-
The font is configured as a CSS variable and then configured on `tailwind.config.js` as the default font for the Website.
599+
The font is configured as a CSS variable and then configured on `packages/ui-components/tailwind.config.ts` as the default font for the Website.
552600

553601
#### Why we use RadixUI?
554602

apps/site/.storybook/main.ts

-75
This file was deleted.

apps/site/.stylelintignore

-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,5 @@ public
1010
# Jest
1111
coverage
1212

13-
# Storybook
14-
storybook-static
15-
1613
# Old Styles
1714
styles/old

apps/site/app/[locale]/next-data/og/[category]/[title]/route.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import HexagonGrid from '@node-core/ui-components/Icons/HexagonGrid';
2+
import JsWhiteIcon from '@node-core/ui-components/Icons/Logos/JsWhite';
13
import { ImageResponse } from 'next/og';
24

3-
import HexagonGrid from '@/components/Icons/HexagonGrid';
4-
import JsIconWhite from '@/components/Icons/Logos/JsIconWhite';
55
import { DEFAULT_CATEGORY_OG_TYPE } from '@/next.constants.mjs';
66
import { defaultLocale } from '@/next.locales.mjs';
77
import tailwindConfig from '@/tailwind.config';
@@ -37,7 +37,7 @@ export const GET = async (_: Request, props: StaticParams) => {
3737
<HexagonGrid style={{ background: gridBackground }} />
3838

3939
<div tw="absolute mx-auto flex max-w-xl flex-col text-center text-3xl font-semibold text-white">
40-
<JsIconWhite width={71} height={80} tw="mx-auto" />
40+
<JsWhiteIcon width={71} height={80} tw="mx-auto" />
4141

4242
<h2>{params.title.slice(0, 100)}</h2>
4343
</div>

apps/site/components/Blog/BlogHeader/index.stories.tsx

-23
This file was deleted.

apps/site/components/Common/BlogPostCard/__tests__/index.test.mjs renamed to apps/site/components/Blog/BlogPostCard/__tests__/index.test.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { render, screen } from '@testing-library/react';
22

3-
import BlogPostCard from '@/components/Common/BlogPostCard';
3+
import BlogPostCard from '@/components/Blog/BlogPostCard';
44

55
function renderBlogPostCard({
66
title = 'Blog post title',

apps/site/components/Common/BlogPostCard/index.tsx renamed to apps/site/components/Blog/BlogPostCard/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import Preview from '@node-core/ui-components/Common/Preview';
12
import { useTranslations } from 'next-intl';
23
import type { FC } from 'react';
34

45
import FormattedTime from '@/components/Common/FormattedTime';
5-
import Preview from '@/components/Common/Preview';
66
import Link from '@/components/Link';
77
import WithAvatarGroup from '@/components/withAvatarGroup';
88
import type { BlogCategory } from '@/types';
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use client';
2+
3+
import type { ActiveLocalizedLinkProps } from '@node-core/ui-components/Common/BaseActiveLink';
4+
import BaseActiveLink from '@node-core/ui-components/Common/BaseActiveLink';
5+
import type { FC } from 'react';
6+
7+
import Link from '@/components/Link';
8+
import { usePathname } from '@/navigation.mjs';
9+
10+
const ActiveLink: FC<
11+
Omit<ActiveLocalizedLinkProps, 'pathname' | 'as'>
12+
> = props => <BaseActiveLink pathname={usePathname()} as={Link} {...props} />;
13+
14+
export default ActiveLink;

0 commit comments

Comments
 (0)