First off, thanks for taking the time to contribute!
The following is a set of guidelines for contributing to @lightningjs/ui-components
. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
- Contributing to
@lightningjs/ui-components
Note: Before you file an issue with a question, you'll get faster results by using the resources below.
Documentation
Discuss on Slack
#lightning-ui-components-support
This section guides you through submitting a bug report for @lightningjs/ui-components
. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
When you are creating a bug report please check the version of LUI that you are using. Include as many details as possible, and post either as an issue here on GitHub.
Note: If you find a Closed issue that seems like it is the same thing that you're experiencing, open a new issue and include a link to the original issue in the body of your new one.
Bugs are tracked as GitHub issues. Create an issue and provide the following information:
- Tell us the specific version of LUI you are using
- Paste code snippets using the markdown code formatters (walls of unformatted copy and pasted text are very difficult to parse)
- Provide links to any relevant code, tickets, or GitHub issues
- Provide screenshots/videos to illustrate the bug you are facing
- Provide detailed reproduction steps
This section guides you through submitting an enhancement suggestion for @lightningjs/ui-components
, including new components, completely new features, and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions.
- Check if there's already a component which provides that enhancement.
- Perform a cursory search to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
Enhancement suggestions are tracked as GitHub issues. Create an issue and provide the following information:
- Use a clear and descriptive title for the issue to identify the suggestion.
- Provide a step-by-step description of the suggested enhancement in as many details as possible.
- Provide specific examples to demonstrate the steps. Include copy/pasteable snippets which you use in those examples, as Markdown code blocks.
- Describe the current behavior and explain which behavior you expected to see instead and why.
- Include screenshots and animated GIFs which help you demonstrate the steps or point out the part which the suggestion is related to. You can use this tool to record GIFs on macOS and Windows, and this tool on Linux.
- Explain why this enhancement would be useful to most
@lightningjs/ui-components
users. - Include any additional information that might be helpful
Unsure where to begin contributing to @lightningjs/ui-components
? You can start by looking through these beginner
and help-wanted
issues:
- [Beginner issues][beginner] - issues which should only require a few lines of code, and a test or two.
- [Help wanted issues][help-wanted] - issues which should be a bit more involved than
beginner
issues.
Follow the instructions below to get started with local development:
Fork and clone the project from here: https://github.com/rdkcentral/Lightning-UI-Components.
In your local terminal, follow these steps:
# enter project directory
cd packages/@lightningjs/ui-components
The Lightning UI Monorepo requires yarn 3.2.3^
The preferred way to manage Yarn is through Corepack, a new binary shipped with all Node.js releases starting from 16.10. It acts as an intermediary between you and Yarn, and lets you use different package manager versions across multiple projects without having to check-in the Yarn binary anymore.
Corepack is included by default with all Node.js installs, but is currently opt-in. To enable it, run the following command:
corepack enable
corepack prepare [email protected] --activate
yarn install
yarn start
Running this command will start storybook which is located in packages/apps/lightning-ui-docs. All changes made in packages/@lightningjs/ui & packages/@lightningjs/ui-components will be reflected in the storybook instance at http://localhost:8000
Before a new component will be reviewed, it must meet the following prerequisite:
- Component concept has been approved by @Lightning/lightning-team
Note: Development on a component may be broken up in separate PRs or done all at once, use your best judgement when breaking up work.
If you are creating a new component, you can bootstrap the required file structure with:
yarn createComponent <packageName> <componentName> <parentName>
packageName
: name of which package the component will be published to (@lightningjs/ui-components
or@lightningjs/ui-components
)componentName
: name of component to be addedparentName
: name of parent component the new components extends
If parentName
is left out, the component's doc will reference an undefined
component which can either be changed to reference another component or deleted
Example: add a new component, MyComponent
, to the @lightningjs/ui-components
package
yarn createComponent @lightningjs/ui-components MyComponent ParentComponent
This will create the following files:
/
└── packages
└── @lightning
└── ui-components
└── src
└── components
└── MyComponent
├── index.js
├── MyComponent.d.ts
├── MyComponent.mdx
├── MyComponent.stories.js
├── MyComponent.styles.js
└── MyComponent.test.js
NOTE: Before RDK accepts your code into the project you must sign the RDK Contributor License Agreement (CLA).
The process described here has several goals:
- Maintain
@lightningjs/ui-components
's quality - Fix problems that are important to users
- Engage the community in working toward the best possible
@lightningjs/ui-components
- Enable a sustainable system for
@lightningjs/ui-components
maintainers to review contributions
Please follow these steps to have your contribution considered by the maintainers:
- When creating a pull request, fill out the default template. Include as much detail as possible, the changes made in the pull request and how to best approach testing the changes
- Follow the style guides
- Follow the new component guide if you are submitting a new component
- After you submit your pull request, verify that all status checks are passing
What if the status checks are failing?
If a status check is failing, and you believe that the failure is unrelated to your change, please leave a comment on the pull request explaining why you believe the failure is unrelated. A maintainer will re-run the status check for you. If we conclude that the failure was a false positive, then we will open an issue to track that problem with our status check suite.
While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted.
As of version 2.7.0, Lightning Core comes bundled with type definitions and in-code documentation which allow you to build Lightning apps in TypeScript. The following documentation assumes that you are already familiar with the basics of writing a Lightning app in JavaScript. Even if you have no Lightning experience, the tips below, the Lightning CLI lng create boilerplate (coming soon), and the types documentation available now in your IDE will be enough to get started.
TypeScript compilation is supported out of the box by Lightning CLI as of version v2.8.0.
Per TypeScript standards all types should be in PascalCase, similar to our class definitions
IMPORTANT: @lightningjs/ui-components
releases are automatically triggered on merge to the release
branch (and possibly other release branches like next
). The new version number is determined by conventional commits which are enforced through husky pre-commit hooks. Read through this section carefully because it will directly impact semantic versioning.
This is the message template:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Breaking Changes
If your feature includes a breaking change (i.e. changes a component API), be sure to add BREAKING CHANGE
to the body of the commit message
Example:
feat(Button): change focus behavior
BREAKING CHANGE
Types
Example: feat: <description>
feat:
new feature or enhancementfix:
bug fixchore:
repo maintenancedocs:
update documentationtest:
test-related changes
Scope
Example: fix(Button): <description>
Use scope to specify which component your changes are related to.
Description
Example: feat(Button): add key-down handler
- Start description with lower-case letter ("do something" not "Do something")
- Use the present tense ("add feature" not "added feature")
- Use the imperative mood ("move cursor to..." not "the cursor was moved to...")
- Limit the first line to 72 characters or less
- Reference issues and pull requests liberally in the body
- When only changing documentation, include
[ci skip]
in the commit title
@lightningjs/ui-components
enforces the Prettier style guide through husky pre-commit hooks.
To run the linter manually:
yarn lint
# to auto-fix lint errors
yarn lint:fix
@lightningjs/ui-components
enforces Jest test coverage of changes through husky pre-commit hooks. 90% coverage of statements, functions and lines are required.
To run tests manually:
# will run tests against both lightning/ui, lightning/ui-core, and lightning/ui-extensions
yarn test
# to run in watch mode
yarn workspace @lightningjs/ui-components test --watch
spyOnMethods is an optional array that may be passed to makeCreateComponent
via the defaultOptions
or options
object. Doing so will extend the component being tested and provide methods that use Jest spies to create mock functions for each method included in the spyOnMethods
array. Each spy method created is a Promise, which resolves after the spied upon method is invoked, and can be awaited in tests.
The most common usage of this is to await the completion of the _update
method in a component being invoked after changes to a component.
Example below:
it('should stop loading once title is set', async () => {
[Button, testRenderer] = createButton({}, { spyOnMethods: ['_update'] });
// ensure _update has completed on the initial render of the component
await Button.__updateSpyPromise;
expect(Button._loading.isPlaying()).toBe(true);
// ensure _update has completed after the title property has been changed
Button.title = 'Action Button';
await Button.__updateSpyPromise;
// assertions may now be made on the state of the component after _update has been called
expect(Button._loading.isPlaying()).toBe(false);
});
Component documentation is covered through a combination of .mdx
and .stories.js
files within each component directory. This guide is intended to help you understand how to add usage documentation, API documentation, and live examples.
Live examples are driven by Storybook stories, and live in <Type>/<Component>/<Component>.stories.js
. If you generated a new component with yarn createComponent
, a template layout should exist for your component.
If you are contributing to an existing component, follow the patterns established there. Adding a story looks like this:
export const StoryName = () =>
class StoryName extends lng.Component {
static _template() {
return {
MyComponent: {
type: MyComponent
}
};
}
};
Run Storybook to see your live examples:
yarn start
Storybook will generate an ID for each story that follows the pattern componentname--storyname
. You can use this ID to embed examples when documenting usage.
This section is dedicated to some specifics around our implementation of Storybook, which is using Storybook v7.5. If you are new to Storybook (or even 7), we highly recommend you check out their Get Started documentation.
You can add interactivity to your documentation with args. For the purposes of this project, you will likely only encounter actions and controls. We're going to walk through a simple example to illustrate the concepts that are necessary to understand for writing stories in the project.
Let's take a simple Button
component. The Button
accepts a title
property and an onEnter
callback.
Our static story would look something like this:
import lng from '@lightningjs/core';
import Button from './Button';
export default {
title: 'Button'
};
export const Basic = args =>
class Basic extends lng.Component {
static _template() {
return {
Button: {
type: 'Button',
title: args.title,
onEnter: args.onEnter
}
};
}
_getFocused() {
return this.tag('Button');
}
};
We can re-work this to support args
so our users can set their own title
in the Controls tab and the 'onEnter'
log in the Actions tab.
export const Basic = args =>
class Basic extends lng.Component {
static _template() {
return {
Button: {
type: 'Button',
title: args.title,
onEnter: args.onEnter
}
};
}
_getFocused() {
return this.tag('Button');
}
};
Basic.args = { title: 'Hello' };
Basic.argTypes = {
onEnter: { action: 'onEnter' },
title: { control: 'text' }
};
There are a few new concepts here: args, argTypes, action, and control.
Let's break down argTypes first. Defining argTypes on the story function is how Storybook knows the controls and actions. onEnter: { action: 'onEnter' }
tells Storybook to set args.onEnter
as a function with the label 'onEnter'
. title: { control: 'text' }
tells Storybook to create a text input control for args.title
.
Shifting focus to args, we can see that we have a definition for 'title'
but not for 'onEnter'
. Setting the args object on a story function tells Storybook what the default values will be for controls defined in argTypes.
Check out the args docs and Controls Essential addon docs for additional details.
Now, let's say you want to execute some side effect when an arg value changes. To do this, we're going to introduce a new concept that is not explicitly a part of Storybook, parameters.argActions
.
You can use argActions
to define functions to execute on changes to an arg. Let's do this for title
:
Basic.parameters = {
argActions: {
title: (title, storyComponent) => {
console.log('Setting title...');
storyComponent.tag('Button').title = title;
}
}
};
In this example, the console will display 'Setting title...'
every time a user changes the value of the title
control.
The callback accepts three arguments:
argValue: any
- current value of the given arg keystoryComponent: lng.Component
- instance of returned class in the storyargs: Object<any>
- complete args object passed into the Story function (the exported anonymous function all of the stories contain)
The render logic for story previews lives in ./.storybook/preview.js
Usage documentation lives in <Type>/<Component>/<Component>.mdx
. MDX allows you to combine markdown and React JSX syntax within the same file!
If you generated a new component with yarn createComponent
, a template layout should exist for your component. If you are contributing to an existing component, follow the patterns established there. Adding usage steps should look like this example:
import { Meta, Title, ArgTypes, Description } from '@storybook/blocks';
import * as ComponentNameStories from './ComponentNameStories.stories';
<Meta of={ComponentNameStories} />
<Title /> /* this will be the H1 on the page */
<Description /> /* taken from JS Doc in Story */
## Usage
Implementation description here
// example implementation
import lng from '@lightningjs/core';
import { MyComponent } from '@lightningjs/ui-components';
class Example extends lng.Component {
static _template() {
return {
type: MyComponent
};
}
}
In summary, documenting usage is as follows:
- description of usage
- [optional] implementation code block
Repeat this pattern for as many usage variations as you see fit.
API documentation lives in <Type>/<Component>/<Component>.mdx
, below usage documentation. MDX allows you to combine markdown and React JSX syntax within the same file!
If you generated a new component with yarn createComponent
, a template layout should exist for your component. If you are contributing to an existing component, follow the patterns established there. Adding API documentation should follow this pattern:
## API
### Parent Properties
ComponentName has the same properties as [ParentComponentName](?path=/docs/components-parentName--docs)
<ArgTypes of={ParentNameStories.parentName} exclude={['mode']} />
### Properties
<ArgTypes of={ComponentNameStories.Basic} exclude={['mode']} />
### Methods
#### methodName(argument:type): returnValue | void
### Style Properties
| name | type | required | default | description |
| ---- | ---- | -------- | ------- | ----------- |
| - | - | - | - | - |