Contribution Guide
When contributing to onyx, please respect the Schwarz IT Code of Conduct and our technical vision/guidelines.
Target audience
This document is directed at people that are developing for onyx. It gives tips and guidelines on what should or must be considered when working with onyx.
Prerequisites / Technical setup
Install Node.js version 22.
We recommend using fnm for managing your node versions which will automatically use the correct node version when working in the onyx repo.Install the pnpm package manager with a compatible version to
^9.12.3
Recommended IDE Setup
We follow the official Vue recommendation for IDE setup which is VSCode with the Vue - Official extension.
Set up the monorepo
In order to work in the onyx monorepo, you need to install the dependencies by running:
pnpm install
Package scripts
Depending on which package you want to contribute, there are different scripts available. A full list can be found in the package.json
file of the corresponding package. Here is a list of the most commonly used scripts.
pnpm install # install all dependencies
pnpm lint:fix:all # lint and fix all packages
pnpm format:all # format all files
pnpm dev # run Storybook in dev mode when developing components
pnpm build # build all onyx components
pnpm test # run unit tests
pnpm test:playwright # run Playwright component tests
pnpm dev # run docs in dev mode
Creating a Pull Request
Pull Requests are very welcome! Please consider our technical guidelines when contributing to onyx.
- Create a fork to commit and push your changes to
- When your changes affect the user and need to be released, add a changeset.
- Then create a PR to merge your changes back into our repository.
When your PR gets approved, you can expect a pre-release immediately after it is merged. Production releases are planned to be published every 2 weeks after the release of version 1.0.0.
Screenshot tests
Component screenshot tests using Playwright will only be performed in our GitHub workflows to ensure consistency of the resulting images which vary on different operating systems.
If you made visual changes to components, you can use this Workflow to update the screenshots on your branch.
Developing Components
Below is the basic code for a new onyx component.
Find more details about:
<script lang="ts" setup>
import type { OnyxComponentProps } from "./types";
import { useDensity } from "../../composables/density";
const props = defineProps<OnyxComponentProps>();
const { densityClass } = useDensity(props);
</script>
<template>
<div :class="['onyx-component', 'onyx-component-name', densityClass]">
<!-- component HTML -->
</div>
</template>
<style lang="scss">
@use "../../styles/mixins/layers.scss";
.onyx-component-name {
@include layers.component() {
// component styles...
}
}
</style>
import type { DensityProp } from "../../styles/density";
export type OnyxComponentProps = DensityProp & {
// component props...
};
import { DENSITIES } from "../../composables/density";
import { expect, test } from "../../playwright/a11y";
import { executeMatrixScreenshotTest } from "../../playwright/screenshots";
import OnyxComponent from "./OnyxComponent.vue";
// #region executeMatrixScreenshotTest
test.describe("Screenshot tests", () => {
executeMatrixScreenshotTest({
name: "OnyxComponent (densities)",
rows: ["default", "hover", "active", "focus-visible", "skeleton"],
columns: DENSITIES,
hooks: {
beforeEach: async (component, page, column, row) => {
/**
* TODO: Prepare the component before the screenshot
* e.g.:
*/
if (row === "hover") await component.hover();
if (row === "focus-visible") await page.keyboard.press("Tab");
if (row === "active") await page.mouse.down();
},
},
component: (column, row) => (
// TODO: Set the props based on the given row and column
<OnyxComponent propName="value" density={column} skeleton={row === "skeleton"} />
),
});
});
// #endregion executeMatrixScreenshotTest
// #region toHaveScreenshot
test("should show hover effect", async ({ mount }) => {
// ARRANGE
const component = await mount(<OnyxComponent propName={value} style="max-width: 8rem;" />);
await component.hover();
// ASSERT
await expect(component).toHaveScreenshot("hover-effect.png");
});
// #endregion toHaveScreenshot
import { withNativeEventLogging } from "@sit-onyx/storybook-utils";
import type { Meta, StoryObj } from "@storybook/vue3";
import { h } from "vue";
import OnyxComponent from "./OnyxComponent.vue";
import OnyxOtherComponent from "./OnyxOtherComponent.vue";
// #region meta
/**
* TODO: Add a general description for this component
*/
const meta: Meta<typeof OnyxComponent> = {
title: "Category/Subcategory/Component",
component: OnyxComponent,
argTypes: {
propName: { control: { type: "text" } }, // TODO: remove or add custom overwrites for properties
...withNativeEventLogging(["onInput", "onChange", "onFocusin", "onFocusout"]),
},
};
export default meta;
// #endregion meta
// #region story
type Story = StoryObj<typeof OnyxComponent>;
/**
* TODO: Add a description for this Story
*/
export const Default = {
args: {
propName: "Value",
},
} satisfies Story;
/**
* TODO: Add a description for this Story
*/
export const Other = {
args: {
...Default.args,
otherPropName: true,
},
} satisfies Story;
// #endregion story
// #region slot
export const WithSlotContent = {
args: {
...Default.args,
slotName: () => [
// The `h` function takes the following form:
// h(componentOrHtmlTag, propertiesAndAttributes, innerTextOrSlotContent)
h(OnyxOtherComponent, { label: "Item 1", href: "/" }),
h("a", { target: "_blank", href: "https://onyx.schwarz" }, "onyx"),
],
},
} satisfies Story;
// #endregion slot