First of all, thank you for your interest in the library. We'd love to accept your fixes and improvements ✨
- Install the latest LTS (min 24) version of Node.js.
- Install pnpm globally by running:
npm i -g pnpm@10. - Set up commit signing for your contributions. Follow these steps:
Please note that our maintainers primarily develop on macOS. While developing on Linux or Windows is possible, there might be unintended issues. Don't hesitate to reach out for help if you encounter any problems.
-
Clone the repository by running:
git clone [email protected]:semrush/intergalactic.git && cd intergalactic. -
Install project dependencies using:
pnpm install.2.1 If you have and error with
libpnglikemake sure that libpng is installed- you need to install it manually. On Mac OS the simplest way is to use homebrew:brew install libpng pkg-config. -
Build components with:
pnpm build.
Follow these steps to submit your changes:
- Fork the repository.
- Apply and commit your changes to your fork.
- Add a note to the appropriate changelog file (for example,
semcore/button/CHANGELOG.md) for each change. Use the date when the changes were made for the new note. - Open a pull request to the main repository.
We appreciate your contributions and will review your pull request as soon as possible. Your efforts make a difference!
We use storybook for developing components.
To get started: Run pnpm storybook and navigate to http://localhost:6006 in your browser to access the playground.
All our examples from the documentation are taken from the storybook stories, from the docs folder.
Also in the component folders, there are also test and advanced folders - the names speak for themselves
We use vitest for our testing needs.
- To prepare docker images for testing, use the command
pnpm test:setup. - To start the test runner, use the command
pnpm test:docker. - To execute tests and exit, use
pnpm test:docker run. - To update snapshots, use the command
pnpm test:docker -- -u. - To run tests for a specific component, use
pnpm test:docker button. - To update snapshots for a specific component, use
pnpm test:docker button -- -u.
We rely on eslint for formatting and linting. It is integrated into our Git hooks and also offers a plugins for VSCode and Webstorm.
To preview the website locally, run pnpm website. The site will be accessible at http://localhost:5173/intergalactic/. Keep in mind that you may need to reload the page to see any changes made to the documentation.
Ensuring the accessibility of our components is a priority. We conduct automated screen reader tests, focusing on VoiceOver screen reader in Safari on macOS. Here's how to set up and run these tests:
- To set up the environment, execute
pnpm vo-test:setup. - Open
voiceOver Utility > Generalon your mac and enable the setting "Allow VoiceOver to be controlled with AppleScript" - Run the tests using the command
pnpm vo-test.
For rapid rebuilding of the playground and website, we use esbuild to efficiently transform components on-the-fly. However, the build process for icon and illustration components is time-consuming and cannot be performed on-the-fly. Therefore, before running the playground or website, it's essential to pre-build icons and illustrations. You can accomplish this using the command pnpm build (or pnpm build:icons and pnpm build:illustration).
Certain components have text translations in multiple languages. When adding or modifying text, focus on English only. Following the pull request review, core maintainers will handle translations for other languages using Crowdin.
Our documentation website's foundation has undergone several iterations and may appear unconventional due to underlying changes. Knowing about its imperfections, we are actively working on migrating to VitePress for a more streamlined development experience.
Each component is published as a distinct npm package with @semcore scope, while a special @semcore/ui package re-exports them collectively. The complex publishing process is fully automated through our CI/CD pipeline.
We rely on a set of design tokens to generate CSS variables (refer to semcore/core/src/theme/themes/default.css). Although all components use these variables, for users it's not mandatory to declare them at the root level of the page. For proper component display, CSS variables' default theme is always included as a fallback value in the var function (for example, color: var(--intergalactic-text-secondary, #6c6e79);). After modifying the name of any CSS variable in component styles, running the pnpm process-theme command is necessary. This command updates the fallback value in var function and is integrated into the pre-commit hook.
Due to historical reasons, our code goes through specific Babel plugins (babel-plugin-root and babel-plugin-styles) that result in the final code functioning differently than it may initially appear. Below is a simplified example of the Link component's code:
import React from "react";
import createComponent, { Component, Root, sstyled } from "@semcore/core";
import { Text } from "@semcore/typography";
import style from "./style/link.shadow.css";
class RootLink extends Component {
static style = style;
render() {
const SLink = Root;
const { styles, Children } = this.asProps;
return sstyled(styles)(
<SLink render={Text} tag="a">
<Children />
</SLink>
);
}
}
const Link = createComponent(RootLink);
export default Link;After normal jsx to js transformation the following expression are getting returned:
sstyled(styles)(
React.createElement(
SLink,
{ render: Text, tag: "a" },
React.createElement(Children)
)
);The babel-plugin-root plugin ensures that all the props passed to a component are also passed to its root wrapper.
- The import of the
Rootvariable from@semcore/coreis removed, as this variable is not really exported from the core. - The
Rootassignment expression is replaced with the value of thetagprop. For instance,const SLink = Root;is transformed intoconst SLink = 'tag';. - The plugin identifies the component's root wrapper based on the variable assigned in the previous step.
- An import statement for the
assignPropsfunction is added, and this function is applied to the props of the root wrapper. The resulting code will look like the following:
sstyled(styles)(
React.createElement(
SLink,
assignProps({ render: Text, tag: "a" }, this.asProps),
React.createElement(Children)
)
);The assignProps function is responsible for smart props merging:
- It spreads the props.
- It merges
refsandforwardRefby encapsulating them within a single ref callback. - It spreads the value of the
styleprop. - It combines
classNameprops. - It wraps event handlers within a single event handler that sequentially calls each event handler. The sequence stops if any event handler returns
false. Outer event handlers are invoked first.
While it's not trivial, this mechanism allows users to apply any props to components. For instance, in the Link component, we don't need to explicitly pass href, target, rel, or other crucial Link props. These are automatically passed using assignProps.
While this mechanism has limitations, two escape hatches are available:
- By adding
__excludeProps={['theme']}, you can prevent thethemeprop from being applied to the DOM node. - By adding
use:theme={modifiedTheme}, you can apply a prop that takes precedence during prop assignment.
Long-term plans involve enhancing this mechanism for more flexible prop passing.
The babel-plugin-styles plugin focuses on CSS transformations. For instance, consider the style file ./style/link.shadow.css for the Link component:
SLink {
display: inline-block;
font-family: inherit;
color: var(--intergalactic-text-link, #006dca);
&[active],
&:hover,
&:active {
color: var(--intergalactic-text-link-hover-active, #044792);
& SText {
border-color: currentColor;
}
}
&[enableVisited]:visited,
&[enableVisited]:visited:hover {
color: var(--intergalactic-text-link-visited, #8649e1);
}
}This CSS file uses nesting and contains selectors targeting tag names used within the component code (for example, SLink).
Here's how the plugin works:
- The plugin analyzes both the CSS and JS code of the component, identifying components prefixed with
S. - It creates hashed CSS classes for the
SLinkcomponent and for each attribute used as a selector in the CSS. - The plugin modifies the component's JS code to add a
classNamelike.SLink-xxx-enableVisitedif theenableVisitedprop is provided. This happens for each selector. - The import statement for the
.shadow.cssfile is removed, and the CSS rules from the file are moved to the compiled JS output. These rules operate like CSS-in-JS, and they are incorporated into the page's stylesheets from the JS code.
If users prefer to include CSS in the final bundle in the traditional way, they can follow the instructions provided in this guide.
All code in this repository is under the MIT License. By sending a pull request you agree to the terms of contributing under the MIT license