Contributing
In this section we will focus on the steps and nuances of developing Kongponents. Lets start with installation.
Installation
Clone the Kongponents repository
git clone https://github.com/Kong/kongponents.git
Install pnpm
. We recommend installing via the command shown here, substituting 9.3.0
with the version listed in the package.json > volta.pnpm
field. For example, for version 9.3.0
you would use the command curl -fsSL https://get.pnpm.io/install.sh | env PNPM_VERSION=9.3.0 sh -
Install dependencies
cd kongponents && pnpm install
Next, let's generate the CLI that can be used to easy scaffold new Kongponent components. This likely was ran automatically after installing dependencies.
pnpm build:cli
Run the docs local dev server with hot-module reload
pnpm docs:dev
Build the docs and preview the built files locally
pnpm docs:preview
Perform a full build of all Kongponents and the Docs site
pnpm build
Perform a full build of all Kongponents and output a /bundle-analyzer/stats-treemap.html
to view the output.
pnpm build:visualize
CLI
It is highly recommended to utilize the included CLI when creating new Kongponents as it will scaffold all the necessary files.
pnpm create-kongponent
Create a new Kongponent
When creating a new component with the CLI it will perform the following actions:
- Creates
/src/components/{KongponentName}/
directory with the following files:{KongponentName}.cy.ts
- Cypress Component Test file{KongponentName}.vue
- Component file
- Adds
/src/components/{KongponentName}/{KongponentName}.vue
to the exports in/src/components/index.ts
- Creates a VitePress markdown file at
/docs/components/{kongponent}.md
(you have to manually add this file to the VitePress sidebar indocs/.vitepress/config.ts
).
NOTE
If your component is exported via an index.ts
file, or anything other than the default {KongponentName}.vue
file, you will need to modify /src/components/index.ts
accordingly.
Once ran, this will be the resulting file structure:
├── docs/
│ └── components/
│ └── {kongponent}.md
└── src/
└── components/
└── {KongponentName}/
├── {KongponentName}.cy.ts
└── {KongponentName}.vue
Important: Type Exports
TIP
As long as you create the new Kongponent via pnpm create-package
this step is automated.
You must manually add the new component type to the module export located at src/global-components.ts
. Just add your new component to the bottom of the list.
// src/global-components.ts
import type * as components from './components'
declare module '@vue/runtime-core' {
export interface GlobalComponents {
KAlert: typeof components.KAlert
KBadge: typeof components.KBadge
KBreadcrumbs: typeof components.KBreadcrumbs
KButton: typeof components.KButton
YourNewComponent: typeof components.YourNewComponent
}
}
Add the doc file to the sidebar
Although the CLI will create a file in the docs directory, the new doc file is not automatically added to the docs sidebar config.
Add the component to the desired location in the sidebar
// docs/.vitepress/config.ts
sidebar: {
// Components sidebar
'/components/': [
{
text: 'Components',
collapsible: true,
items: [
...
{
// The name of the rendered element, e.g. "Alert"
text: '{Component Name}',
// The name of the `.md` markdown file without the extension, e.g. "/components/alert.md"
link: '/components/{component-name}',
},
...
]
}
]
}
Edit the Doc file
Each component has an associated file in the /docs/components
directory. After scaffolding the new component file, a doc file should be present named the same as your new component. Below are the steps to add the file to the docs site and how to get started editing.
Renaming the file (if needed)
The docs markdown file should be named correctly if generated from the create-kongponent
CLI. If necessary, rename the file to correspond to what type of component it is. For documentation purposes page names should be based on what the component is vs its Kongponent K
name.
Examples
kbutton.md
→button.md
kcard.md
→card.md
kdatetimepicker
→datetime-picker.md
Update the page title
Update the first line of the doc to match the file name. This is what is displayed as the page title & in the sidebar.
# {Name}
**{KongponentName}** - description
Importing type declarations and interfaces
When importing type declarations or interfaces, you can use a relative path instead of the @/
alias so that the types are properly resolved within consuming packages. See the example below:
import { StepperState } from './KStepState.vue'
This repository utilizes tsc-alias
to replace these paths during the build; however, either method is acceptable.
Style guidelines
In order to prevent component styles from leaking out into the consuming application, all component styles MUST adhere to one of the following rules:
(Preferred) All styles must be
scoped
within your components with<style lang="scss" scoped>
.- If you need to target nested components (e.g. Kongponents) to override styles, you'll need to utilize deep selectors
html<style lang="scss" scoped> .your-component-class { :deep(.k-button) { /* KButton override styles go here */ border-color: red; } } </style>
All component styles must be wrapped in a unique wrapper class so that styles do not leak out into the consuming application.
TODO: update class naming guidelines
The class name should follow the syntax `.k-{component-name}-*`
This is a good practice even if you go with option one outlined above.
```html
<style lang="scss">
.k-button {
/* All other styles must go inside the wrapper */
}
</style>
```
Relative units
Kongponent styles should never use relative font units; specifically, do not use rem
or em
units.
We cannot control the html
base font size and therefore these relative units are not predictable within a host application. Use px
(pixels) or a similar unit instead.
Code best practices
Prop naming
It's essential to choose context-aware and descriptive names. This practice ensures that the purpose and usage of each prop are clear to other developers. Additionally, avoid using prefixed props, as they can lead to redundancy and confusion. For example, instead of using hasError
, simply use error
, and replace isSelected
with selected
. This approach not only simplifies the prop names but also enhances the readability and maintainability of our codebase.
Attributes
Sometimes you will need to generate a random string to be used as value for various attributes (e.g. accessibility-related attributes like id
, for
, aria-labelledby
, etc.).
To generate a unique id so that it is safe for SSR, you must use the useId
composable in your component:
<script setup lang="ts">
import { useId } from 'vue'
const id = useId()
</script>
Note that useId
can only be used at the root of the setup function. If you need a random ID in a template or in any of your component functions (e.g. to give each key
in a v-for
loop a unique value), you should use the getUniqueStringId
helper function:
<template>
<div v-for="item in items" :key="item.id">
<!-- some content -->
</div>
</template>
<script setup lang="ts">
import { getUniqueStringId } from '@/utilities'
const items = myArray.map(item => ({
...item,
id: getUniqueStringId(), // add a unique id
}))
</script>
Testing your component
You're free to play around with your component on the local instance of the docs site by running pnpm docs:dev
; however, you may also want to test your local changes in a consuming application.
Run
yarn link
from the root of the Kongponents repositoryshyarn link yarn link v1.22.17 success Registered "@kong/kongponents". info You can now run `yarn link "@kong/kongponents"` in the projects where you want to use this package and it will be used instead. ✨ Done in 0.04s.
Build the Kongponents package
shpnpm build:kongponents
Note
Alternatively, if you do not need to rebuild the types and would prefer to watch for changes, you can run just the
vite build
command with the--watch
flag to automatically rebuild when you save changes.shpnpm vite build --watch
As the instructions above outline, next, inside your consuming application, run
yarn link "@kong/kongponents"
shyarn link "@kong/kongponents" yarn link v1.22.19 success Using linked package for "@kong/kongponents". ✨ Done in 0.04s.
Now that the dependency is linked, your local project will utilize the local build.
Note
You may need to clear the
vite
cache using the--force
flag in your host app in order to pick up the newly built files. You will also need to restart your Vite server after every rebuild of Kongponents to pull in the changes.TIP
If your project utilizes Vite, you may need to dedupe your dependency tree to avoid errors when running locally. Inside your
vite.config.ts
file, insert the following configuration:tsexport default defineConfig({ resolve: { // Use this option to force Vite to always resolve listed dependencies to the same copy (from project root) // Allows utilizing `yarn link "{package-name}"` without throwing errors dedupe: ['vue'] }, })
When you're finished testing, don't forget to run
yarn unlink
inside the Kongponents directory.shyarn unlink yarn unlink v1.22.17 success Unregistered "@kong/kongponents". info You can now run `yarn unlink "@kong/kongponents"` in the projects where you no longer want to use this package. ✨ Done in 0.04s.
At this point it is recommended you delete the
node_modules
folder in your consuming app and rerunpnpm install
to fully clear the linkage.
Sandbox
The sandbox mode in Kongponents provides developers with a controlled environment to test and ensure that no styles from the docs app leak through into the component library. This feature guarantees that the components look and function as intended, without any external interference.
By using the sandbox mode, you can have confidence that components maintain their visual consistency, regardless of the context in which they are used.
pnpm dev:sandbox
Committing Changes
This repo uses Conventional Commits.
Commitizen and Commitlint are used to help build and enforce commit messages.
It is highly recommended to use the following command in order to create your commits:
pnpm commit
This will trigger the Commitizen interactive prompt for building your commit message and will allow you to preview your commit.
Enforcing Commit Format
Lefthook is used to manage Git Hooks within the repo. See see the current /lefthook.yaml
here:
# Reference:
# https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md
pre-push:
parallel: true
commands:
stylelint:
skip:
- merge
- rebase
run: pnpm stylelint
eslint:
skip:
- merge
- rebase
run: pnpm lint
commit-msg:
parallel: true
commands:
commitlint:
skip:
- merge
- rebase
run: pnpm commitlint --edit "$1"
A commit-msg
hook is automatically setup that enforces commit message stands with commitlint
.
A pre-push
hook is configured to run Stylelint and ESLint before pushing your changes to the remote repository.
Recommended IDE Setup
We recommend using VSCode along with a few suggested extensions (VSCode should automatically show a notification suggesting to install the recommended extensions when you open the project folder for the first time).
NOTE
To utilize all the pre-configured VSCode workspace settings for this project, we recommend opening the containing workspace folder directly, instead of opening it through VSCode Workspace.
Type interfaces for Vue components
All types related to components must be included in the /src/types/
folder in a corresponding {Kongponent-name}.ts
type file (without the k
prefix).
All files must be exported from the /src/types/index.ts
file.
── src/
└── types/
├── {Kongponent-name}.ts
└── index.ts
When importing a type for use in a component, we use import with type
prefix
import type { SomeType } from '@/types'
Tips when typing Vue components
- Instead of setting prop type as
String
, set it as String Literal Types e.g. when we want to set via prop amode
, we can set it aslight | dark | none
instead ofString
- Avoid using
Object
type as props, use an interface instead.