Skip to content

Latest commit

 

History

History
541 lines (367 loc) · 17 KB

contributing.md

File metadata and controls

541 lines (367 loc) · 17 KB

Contributing

Basic tutorial

This guide is intended to help you get started with contributing to the project. By following these steps — which should take no more than 30 minutes —, you will understand the development process and workflow.

  1. Cloning the repository
  2. Installing Node.js and npm
  3. Installing dependencies
  4. Starting the development server
  5. Creating a component
  6. Creating the default example
  7. Styling the example
  8. Testing the example
  9. Writing another example
  10. Importing styles from other examples
  11. Promoting the component
  12. Updating the examples to import from @ariakit/react
  13. Writing the component documentation
  14. Writing documentation for other examples
  15. Submitting a pull request

Advanced tutorial

This guide covers more advanced topics. Pick the topics based on your needs.

  1. Versioning
  2. Running with React 17
  3. Writing end-to-end tests



Cloning the repository

To start contributing to the project, you have to fork this repository and clone it to your local machine:

git clone https://github.com/YOUR_USERNAME/ariakit.git

If you are already part of the organization on GitHub, clone the repository directly:

git clone https://github.com/ariakit/ariakit.git

Alternatively, you can open the project in Gitpod and skip to Creating a component.

Installing Node.js and npm

This repository uses npm workspaces to manage multiple ESM projects. You need to install npm v7 or higher and Node.js v18 or higher.

You can run the following commands in your terminal to check your local Node.js and npm versions:

node -v
npm -v

If the versions are not correct or you don't have Node.js or npm installed, download them from https://nodejs.org.

Alternatively, you can use nvm to install the project's Node.js and npm versions. Once in the project's root directory, run the following command in your terminal:

nvm use

If you haven't installed the specific Node.js version yet, nvm will ask you to run nvm install to install it. Follow the instructions in your terminal.

Installing dependencies

Once in the project's root directory, run the following command to install the project's dependencies:

npm install

Starting the development server

After installing the project's dependencies, run the following command to start the development server:

npm run dev

If you're on Windows, we recommend using WSL or Gitpod.

Now open http://localhost:3000 in your browser to see the project's site.

Creating a component

To make a new component, create a file with the following contents:

packages/ariakit-react-core/src/my-component/my-component.tsx

import type { ElementType } from "react";
import { createElement, createHook, forwardRef } from "../utils/system.tsx";
import type { Options, Props } from "../utils/types.ts";

const TagName = "div" satisfies ElementType;
type TagName = typeof TagName;

/**
 * Description for my component hook.
 * @see https://ariakit.org/components/my-component
 * @example
 * ```jsx
 * const props = useMyComponent();
 * <Role {...props} />
 * ```
 */
export const useMyComponent = createHook<TagName, MyComponentOptions>(
  function useMyComponent({ customProp = "My component", ...props }) {
    props = { children: customProp, ...props };
    return props;
  }
);

/**
 * Description for my component.
 * @see https://ariakit.org/components/my-component
 * @example
 * ```jsx
 * <MyComponent />
 * ```
 */
export const MyComponent = forwardRef(function MyComponent(
  props: MyComponentProps,
) {
  const htmlProps = useMyComponent(props);
  return createElement(TagName, htmlProps);
});

export interface MyComponentOptions<_T extends ElementType = TagName>
  extends Options {
  /**
   * Description for custom prop.
   */
  customProp?: string;
};

export type MyComponentProps<T extends ElementType = TagName> = Props<
  T,
  MyComponentOptions<T>
>;

That's the basic structure for all components in the project. This will guarantee that the component will support all the library's core features. You can take a look at other components to see more complex examples.

Creating the default example

The development workflow on this project is entirely based on examples. You can think of an example as a use case of a component. This will be used not only for development purposes, but also to show the component and its usage in the documentation.

Every component has a default example that receives the name of the component. This default example should be a common use case, but also simple enough so it requires as few custom props as possible.

Let's create a default example for our component:

examples/my-component/index.tsx

import { MyComponent } from "@ariakit/react-core/my-component/my-component";

export default function Example() {
  return <MyComponent />;
}

Now open http://localhost:3000/examples/my-component to see the example in action.

Styling the example

When necessary, you can apply styles to the example. We're using Tailwind to keep the styles consistent throughout the project. You will find the theme configuration in the tailwind.config.cjs file.

To use Tailwind in a CSS file rather than applying classes directly to the HTML elements, we're using the @apply directive.

Make sure you also take dark mode into account.

examples/my-component/style.css

.my-component {
  @apply
    bg-red-600
    text-white
    dark:bg-red-800
}

Now we need to import the CSS file on the example's index.tsx file and add the class name to the respective elements:

examples/my-component/index.tsx

import "./style.css";
import { MyComponent } from "@ariakit/react-core/my-component/my-component";

export default function Example() {
  return <MyComponent className="my-component" />;
}

Now open http://localhost:3000/examples/my-component to see the example with the styles applied.

You'll notice that the transpiled CSS file has been also added to editor's files so people can easily edit it directly in the browser. You can also use it to see the output CSS while applying Tailwind classes.

Testing the example

One of the goals of having use cases written like that is so we can write automated tests for them. Instead of testing the Ariakit components directly, we're testing the examples that represent the way people use Ariakit components.

We use @ariakit/test, which is a wrapper around React Testing Library with some additional features to ensure that events like clicks and key presses work similarly to actual user events.

Let's create a test for our example:

examples/my-component/test.ts

import { q } from "@ariakit/test";

test("my component", () => {
  expect(q.text("My component")).toBeInTheDocument();
});

Now run the following command in your terminal to watch the test results:

npm test watch my-component

Writing another example

A component may have multiple examples besides the default one. This is useful when you want to show a component in different contexts and props.

Conventionally, the example names are prefixed with the component name and a short suffix. For example: my-component-custom-prop.

Let's create another example for our component:

examples/my-component-custom-prop/index.tsx

import "./style.css";
import { MyComponent } from "@ariakit/react-core/my-component/my-component";

export default function Example() {
  return <MyComponent className="my-component" customProp="Hello world" />;
}

Importing styles from other examples

We can @import CSS files from other examples. You'll usually import the styles from the default example into the other examples so you don't need to repeat the same base styles.

examples/my-component-custom-prop/style.css

@import url("../my-component/style.css");

.my-component {
  @apply
    p-4
}

Now open http://localhost:3000/examples/my-component-custom-prop to see the example with the custom prop applied.

Promoting the component

So far, we've been working with the @ariakit/react-core package. This is where all the components — including experimental stuff — are developed. This package doesn't follow semver, so we can introduce breaking changes on patch and minor updates. Once a component is stable enough, it's promoted to the @ariakit/react package.

To promote our component to the @ariakit/react package, we need to create a file and re-export the component from the @ariakit/react-core package:

packages/ariakit-react/src/my-component.ts

export { MyComponent } from "@ariakit/react-core/my-component/my-component";

export type {
  MyComponentProps,
  MyComponentOptions,
} from "@ariakit/react-core/my-component/my-component";

Finally, we must update the index.ts file to export the component:

packages/ariakit-react/src/index.ts

// ...
export * from "./my-component.ts";
// ...

Updating the examples to import from @ariakit/react

Now that we've promoted our component to the @ariakit/react package, we need to update the import declarations on the examples:

examples/my-component/index.tsx

import "./style.css";
import * as Ariakit from "@ariakit/react";

export default function Example() {
  return <Ariakit.MyComponent className="my-component" />;
}

examples/my-component-custom-prop/index.tsx

import "./style.css";
import * as Ariakit from "@ariakit/react";

export default function Example() {
  return (
    <Ariakit.MyComponent className="my-component" customProp="Hello world" />
  );
}

Writing the component documentation

Now that the component is part of the main library, we should write documentation for it.

We can create a markdown file in the components folder and render an anchor element pointing to the example's index file with a data-playground attribute. This will turn the link into a playground.

components/my-component.md

# My component

<div data-description>

This is my component.

</div>

<a href="../examples/my-component/index.tsx" data-playground>Example</a>

Now open http://localhost:3000/components/my-component to see the component documentation.

Writing documentation for other examples

Unlike default examples, other examples will be primarily accessed through their own URLs (for example: http://localhost:3000/examples/my-component-custom-prop). To write documentation for them, we can create a readme.md file in the example's directory and follow the same convention as for the component's markdown file.

examples/my-component-custom-prop/readme.md

# My component with `customProp`

<div data-description>

This is my component using `customProp`.

</div>

<a href="./index.tsx" data-playground>Example</a>

Note that we're passing the `customProp` prop to the component:

```tsx
<MyComponent className="my-component" customProp="Hello world" />
```

Now open http://localhost:3000/examples/my-component-custom-prop to see the example documentation.

Submitting a pull request

When you're ready to submit a pull request, you can follow these naming conventions:

  • Pull request titles use the Imperative Mood (e.g., Add something, Fix something).
  • Changesets use past tense verbs (e.g., Added something, Fixed something).

When you submit a pull request, GitHub will automatically lint, build, and test your changes. If you see an ❌, it's most likely a bug in your code. Please, inspect the logs through the GitHub UI to find the cause.



✅ Now you're ready to contribute to the project. Follow the next steps if you need more advanced instructions.


Versioning

When adding new features or fixing bugs, we'll need to bump the package versions. We use Changesets to do this.

The action of adding a new example doesn't require a version bump. Only changes to the codebase that affect the public API or existing behavior (e.g., bugs) do.

Let's craft a fresh changeset file for our component. You can use any name for the file, but it should begin with the pull request number, followed by a dash:

.changeset/1271-my-component.md

---
"@ariakit/react": minor
"@ariakit/react-core": patch
---

Added `MyComponent` component.

Once your pull request is merged into the main branch, the Publish PR will be automatically created/updated with the new changes. Once we merge this PR, the affected packages will be automatically published to npm and the changelog will be updated.

Running with React 17

Ariakit supports both React 17 and React 18. If you want to see if your example works with React 17, you can run the following commands.

npm run test-react17

This command will automatically re-install React 18 at the end of the process. If, for some reason, this doesn't happen automatically, you should run npm i in your terminal.

Writing end-to-end tests

Most of the time, we'll write unit and integration tests as described on Testing the example. Those tests simulate real user interactions, but they don't run in the browser. They use JSDOM, which implements JavaScript DOM APIs in a Node.js environment.

Combined with the @ariakit/test package, this is more than enough for most cases. However, sometimes we need a real browser to test specific interactions with our examples that aren't supported in JSDOM. For those cases, we use Playwright.

Let's create an end-to-end test for our example:

examples/my-component/test-chrome.ts

import { expect, test } from "@playwright/test";

test.beforeEach(async ({ page }) => {
  await page.goto("/previews/my-component");
});

test("my component", async ({ page }) => {
  const element = await page.getByText("My component");
  await expect(element).toBeVisible();
});

Now run the following command in your terminal to see the test results (make sure to replace my-component with the name of your example, or omit it to run all the tests):

Note: The development server must be running in another terminal instance.

npm run test-browser my-component

You can also run the tests in headed mode:

npm run test-browser-headed my-component

Or in debug mode:

npm run test-browser-debug my-component