Warning This project is no longer maintained and has been archived.
This repository is deprecated and is no longer actively maintained. We recommend using PrimeVue for similar functionality.
- Last Update: Sep 5, 2024
- Reason for Deprecation: Moving forward, the project will now be tightly coupled with Lob's internal Dashboard and design system, and will be maintained in that repository.
- Recommended Alternative: PrimeVue
Note: This README now serves as an archive. The information below may be outdated.
[Original README content starts here]
Lob's Vue component library. This repo is public. We are building our components in public to showcase the work that Lob is doing to create a consistent design system as well as follow best practices in modern web development and accessibility.
This component library can be found at https://ui-components.lob.com/.
npm install @lob/ui-components
Add the following code in your own project's entry file (e.g. main.js
) This will allow you to use the components anywhere.
import { createApp } from 'vue';
import components from '@lob/ui-components';
import '@lob/ui-components/dist/ui-components.css';
createApp(App).use(components).mount('#app');
If you would like to use the same Tailwind configuration in your project as is used in this component library, you can install and configure it via a plugin in your tailwind.config.js
. Note, you'll need to have already installed Tailwind.
npm install --save-dev tailwind-plugin-lob
module.exports = {
plugins: [require('tailwind-plugin-lob')]
// If you need to specify other options, you can set other options
};
This component library supports optional (though strongly encouraged 🙃) internationalization with vue-i18n.
Without vue-i18n in your application, labels and screen reader instructions will be in English. To provide different translations or translations in other languages, install vue-i18n and put the following code in your app's entry file (e.g. main.js
)
npm install vue-i18n@next
import { createApp } from 'vue';
import { createI18n } from 'vue-i18n';
import components from '@lob/ui-components';
import '@lob/ui-components/dist/ui-components.css';
// we recommend putting each locale object in its own file (i.e. in src/locales and exporting)
const messages = {
en: {
message: {
hello: 'hello world'
}
},
es: {
message: {
hello: 'hola mundo'
}
}
};
const i18n = VueI18n.createI18n({
locale: 'es', // set locale, we recommend creating a function to get the user's default local
fallbackLocale: 'en', // set fallback locale, we recommend putting this in your env vars
messages // set locale messages
// If you need to specify other options, you can set other options
// ...
});
createApp(App).use(i18n).use(components).mount('#app');
Before installing node modules, make sure you have the node and npm versions installed matching what's listed in the engines
in package.json
.
The path to the src
directory is aliased as @
so we can avoid relative paths in our imports. This makes our codebase more resilient as it grows because if we refactor and move files around, we won't have to spend as much time fixing relative path imports.
When applicable, split components up across multiple smaller components. For example, don't put every element of the nav bar into one component called NavBar
- split them up into a NavBarListItem
and the nav bar itself.
When you're not sure how many children a parent component may have, default to using slots so that it's as extendable as possible.
We try to avoid custom scoped styles in our components and use the Tailwind classes everywhere possible.
If your designs are off by a pixel or two from Tailwind presets, we highly recommend using the Tailwind presets instead of creating one-off styles.
There are some minimal use cases where scoped styles are preferable
- When you need to style a
::before
or::after
pseudo element - When you need styles based on siblings (
+
or~
selectors) - When you have styles that truly don't appear anywhere else in the designs (this should happen extremely rarely thought we've seen it most often with box-shadows)
If your component has a single Story, we use the default "Primary" naming convention.
If your component has multiple stories, we name each story semantically to describe the use case.
If you need a wrapper container around your story (for example, because you have multiple elements, or you need to apply styling to the container), use a decorator.
See the main navigation component story file for an example.
We're using .mdx
files for documentation for each component. This allows us to use markdown to write more detailed docs but also integrate with Storybook's docs add-on blocks (e.g. things like automatically generating a preview of your component, the props table, etc.).
At the minimum, each component's docs should have:
- A preview section
- An intro to the component - what it'd most commonly be used for
- How to use the component, with code examples
- A props section with the types and required fields complete
We've included Vue Router and a routeDecorator
(.storybook/routeDecorator.js
) so you can use dynamic routing in your components. If your component needs to be route-aware, either because it uses router-link
or it checks this.$route
in a computed prop or method, you'll need the route decorator in your Story.
See the main navigation and subnavigation component story files for examples.
While we're not strictly enforcing any particular WCAG standard (though we should strive for at least AA), we want to achieve a base-level of accessibility with our first iteration. This includes:
- Keyboard navigable
- Sensical for screen reader users
- Color contrast passes AA at least
npm run storybook
npm run test
npm run lint
We consider a component PR complete when it has the following:
- Storybook examples with the ability to trigger each state
- Clear Storybook documentation
- Base level of accessibility
- Tests added/updated for any code added/updated
- Existing tests & lint passes (integrated with GitHub Actions, but you can also run both locally)
Our SVG Icons require a range of sizes from 14px to 24px. For convenience, you can use the following shell script to generate resized copies of an svg at the relevant sizes:
#!/bin/bash
SIZES=(14 16 18 20 24)
for FILENAME in "$@"
do
echo "Processing $FILENAME"
for SIZE in "${SIZES[@]}"
do
echo "Resizing to $SIZE"x"$SIZE"
NEW="$(echo "$FILENAME" | sed -e "s/svg$/$SIZE.svg/")"
rsvg-convert "$FILENAME" -w $SIZE -h $SIZE -f svg -o "$NEW"
done
echo "Done processing $FILENAME"
done
# e.g. ./ResizeIcons.sh arrow_icon.svg house_icon.svg
Note: You must have librsvg installed to use this script:
brew install librsvg
When a PR is opened Amplify will automatically build and deploy a PR preview. You will see the URL for the preview in a comment from the Amplify bot in the PR.
When the PR is merged to main
Amplify will auto build and deploy to https://ui-components.lob.com/.
We have a GitHub Action set up to publish this package automatically to NPM when the version number in package.json
is incremented on the main
branch.
If you need to publish manually, you can do:
npm run build
npm publish