Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@leaflink/stash

Package Overview
Dependencies
Maintainers
0
Versions
3233
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@leaflink/stash

LeafLink's design system.

  • 49.4.1
  • latest
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
1.5K
decreased by-73.19%
Maintainers
0
Weekly downloads
 
Created
Source

@leaflink/stash

Interactive building blocks for creating user interfaces.

version downloads Contact Us semantic-release Commitizen friendly

Stash is a collection of primitive, product-agnostic elements that help encapsulate LeafLink's look and feel at base level. This project is intended to be used across our digital product portfolio.

Table of Contents

Quick Start

Stash requires Vue 3 and Tailwind CSS 3. To get started, install the package and its peer dependencies:

npx install-peerdeps @leaflink/stash

Then, import the package and its styles in your app. Load the base and components styles from Stash, and then add your app's styles after them.

import { createApp } from 'vue';
import stash from '@leaflink/stash';

// stash styles
import '@leaflink/stash/styles/base.css';
import '@leaflink/stash/components.css';

// app styles
import './app.css';

const app = createApp(App);

app.use(stash);

Your app styles should be loaded after the Stash styles to ensure that they override the Stash styles. At minimum, your app styles should include the following:

@tailwind base;
@tailwind components;
@tailwind utilities;

Configure Tailwind in your app by creating a tailwind.config.ts file. It should include the Stash files in the content setting.

import stashPreset from '@leaflink/stash/tailwind-base';

/** @type {import('tailwindcss').Config} */
export default {
  presets: [stashPreset],

  content: ['./src/**/*.{vue,ts,js}', './node_modules/@leaflink/stash/dist/*.js'],
};

See the Tailwind section for more details on how to configure it in your app.

[!NOTE] For apps still requiring deprecated css & utility classes, you can include the backwards compat styles from Stash:

import '@leaflink/stash/styles/backwards-compat.css'; // Add this line before the base and components styles.
import '@leaflink/stash/styles/base.css';
import '@leaflink/stash/components.css';

import './app.css';

Also, if you still need legacy Stash sass variables, functions, and mixins in your components, you can configure Vite to import them:

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), '');

  return {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: '@use "sass:map"; @import "@leaflink/stash/styles/core";',
        },
      },
    },
  };
});

Usage

@leaflink/stash is a Vue component library that implements Leaflink's Stash Design System. So every one of LeafLink's colors, typography, shadows, etc. can be accessible via tailwind utility classes like tw-text-blue-500 tw-text-sm.

Stash is a Vue plugin that can be installed in your app. You do not need to install the plugin in order to use the components, but it is required if you need to configure the framework to suit your specific needs.

There are several options to configure the framework to suit your specific needs, and they are all optional. Any options you pass to the plugin will be merged with the default options & applied to the entire framework.

[!WARNING] If you don't install the plugin in your app, you will need to manually setup modals, toasts, and other features that require some setup that's normally done for you by the Stash plugin.

interface StashPluginOptions {
  /**
   * Translation options, language and locale
   */
  i18n?: I18nPlugin;

  /**
   * Setup methods to persist user-settings. There are several LocalStorage helpers which may be overridden
   */
  storage?: {
    set: <T = unknown>(name: string, data: T, options?: { [key: string]: unknown }) => void;
    get: <T = unknown>(name: string, options?: { [key: string]: unknown }) => T;
  };

  /**
   * Path to static assets such as icons and illustrations
   */
  staticPath?: string;

  /**
   * Image options
   */
  images?: StashOptionImages;

  /**
   * Google Maps API key
   */
  googleMapsApiKey?: string;

  /**
   * Modals options
   */
  modals?: false | ModalsPluginOptions;

  /**
   * Toasts options
   */
  toasts?: false | ToastsPluginOptions;
}

interface StashOptionImages {
  provider: StashImageProviders; // 'cloudinary' | 'static'
}

interface ModalsPluginOptions {
  mountNodeClass?: string;
  mountNodeId?: string;
}

interface ToastsPluginOptions {
  mountNodeClass?: string;
  mountNodeId?: string;
}

Example

A sample configuration might look something like:

// src/main.ts
import { createApp } from 'vue';
import stash from '@leaflink/stash';
import i18n, { locale } from 'path/to/i18n';

const app = createApp(App);

app.use(stash, {
  i18n: {
    locale,
    t: (key, value) => i18n.t(key, value),
  },
  googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API,
});

This example will load the core i18n options and Google Maps api key.

npm scripts

Most operations are run using npm run and are defined in package.json. Check out the available scripts with npm run.

A few commonly used commands are:

  • npm run docs will start the docs using Vitepress dev server on port 5180.
  • npm run lint will lint all vue/js files with eslint & lint css with stylelint. Optionally you can just lint js or css with the lint:js and lint:css scripts respectively. You can run npm run lint --fix to auto-fix code styles.
  • npm test <file> when we want to run a single spec during active development.
  • npm test runs all unit and integration tests with Vitest. --watch is enabled by default and you can pass any other Vitest options to this script that you'd like.
  • npm run test:ci will run tests and generate coverage. Used in CI, but nothing stopping you from using it locally if you want to run with coverage.
  • npm run type-check will run the TypeScript compiler and perform a static analysis of the code and report any type errors it finds.
  • npm run build will build the application and prepare it for production.
  • npm run build-ts will build the application, perform a static analysis to find any type errors and prepare it for production.

Legacy Styles

@leaflink/stash exposes a stylesheet for backwards compatibility with legacy stash utilities. This stylesheet includes styles for components that have been deprecated or removed from the Stash Design System. It is not required for greenfield projects.

/* legacy stash styles - not required for greenfield projects */
import '@leaflink/stash/styles/backwards-compat.css';

/* stash styles */
import '@leaflink/stash/styles/base.css';
import '@leaflink/stash/components.css';

import './app.css';

Tailwind

@leaflink/stash uses Tailwind behind the scene to style its components. It's currently required to run this library downstream in order to avoid issues with css duplication & ordering.

In order to avoid class name clashes with legacy utilities, @leaflink/stash prefixes Tailwind utility classes with tw-. This may change in the future when all LeafLink apps no longer need deprecated styles. For now, when needing to use a tailwind class, just add the tw prefix like so: tw-flex md:tw-text-blue.

import Button from '@leaflink/stash/Button.vue';
import IconLabel from '@leaflink/stash/IconLabel.vue';

<Button icon-label class="tw-hidden md:tw-inline tw-ml-3">
  <IconLabel icon="user-add" title="Add Recipient" size="dense" stacked>
    Add Recipient
  </IconLabel>
</Button>;

Configuration

In order to properly use Tailwind with Stash, it's necessary to add Stash's base styles as a preset of your project.

// tailwind.config.ts

import stashPreset from '@leaflink/stash/tailwind-base';

/** @type {import('tailwindcss').Config} */
export default {
  presets: [stashPreset],

  content: ['./src/**/*.{vue,ts,js}', './node_modules/@leaflink/stash/dist/*.js'],
};

Resources

  • index.js: This is the "install" entry point, for use with app.use(...).
  • components: All components
  • composables: Similar to mixins or React's "Hooks", but for a Vue component
  • constants: LeafLink global constants
  • directives: Vue directives
  • plugins: Vue plugins
  • styles: SCSS, CSS, style utils, etc.
  • types: TypeScript type declarations
  • utils: Includes various helpers for internal and external use

Core files & Entry Points

index.js is used as the main entry point to the framework. It also exports each component individually, for an à la carte build. You may pull in the default export directly and app.use it (to quickly get up and running w/ all components and features); or, you may wish configure it with particular options, components, or features.

à la carte

@leaflink/stash serves its components and directives à la carte, which means that instead of importing the entire library, you selectively import only the specific components and directives that you need for your project. This approach helps reduce the bundle size of your application, resulting in faster load times and improved performance.

// Component.vue

import Select from '@leaflink/stash/Select.vue';

<Select></Select>;
// Component.vue

import autofocus from '@leaflink/stash/autofocus';

<button v-autofocus>Click</button>;

Peer dependencies

Peer dependencies are specific dependencies that a package requires to work correctly, but expects the consumer of the package to provide. In other words, they are dependencies that the package relies on, but are not bundled with the package itself.

@leaflink/stash project requires some peer dependencies:

  • lodash-es: The utility library is required as a peer dependency as an optimization to reduce the bundle size. Required compatibility with this package on version ^4.x.

  • tailwindcss: Our utility-first CSS framework used for building our responsive and customizable components. Required compatibility with this package on version ^3.3.1 or higher.

  • vue-router: The official router for Vue.js applications. Required compatibility with this package on version ^4.x or higher.

These peer dependencies need to be installed separately by the consumer of the package, ensuring that the correct versions are used to maintain compatibility and avoid conflicts with other dependencies in the project.

Testing

[!TIP] If you are contributing to @leaflink/stash, please refer to this contributing testing section for more details on how to properly test your changes.

To run tests, there's multiple npm scripts available to you:

  • npm run test - Run all tests in watch mode.
  • npm run test <file> - Run matching spec files quickly and watch for changes.
  • npm run test:ci - Run tests and generate coverage. Used in CI, but nothing stopping you from using it locally if you want to run with coverage.

They all run Vitest but are typically used at different times because of the default Vitest options we're passing to them inside of the npm script. All of them allow you to pass any additional Vitest cli options that you'd like, i.e. npm run test -- --silent.

Testing Library truncates the output from tests, which can cut off large DOM elements logged to the console. This limit can be adjusted with the DEBUG_PRINT_LIMIT environment variable, which can be set either when running tests (DEBUG_PRINT_LIMIT=100000 npm run test) or added to your shell's profile (export DEBUG_PRINT_LIMIT=100000) to make it the default. For more on debugging with Testing Library, see official documentation.

Coverage HTML reports are written to ./coverage.

Run open ./coverage/index.html from the root of the repo to pop open the report in your browser of choice.

To test @leaflink/stash components, it's necessary to expose stash as a plugin on the global config test object.

// setup-env.ts

import stash from '@leaflink/stash';
import { config, flushPromises } from '@vue/test-utils';

config.global.plugins = [[stash, { googleMapsApiKey: 'my-key' }]];

Mocking Google Maps API when testing AddressSelect

When testing components that use the AddressSelect component or useGoogleMaps composable, it's necessary to mock it. This is because the useGoogleMaps composable uses the google.maps global object, which is not available in the testing environment.

The easiest way to do this is to mock the useGoogleMaps composable and avoid trying to mock the Google Maps API directly.

Create a file in the __mocks__ directory of the @leaflink/stash package, and mock the useGoogleMaps composable.

/* __mocks__/@leaflink/stash/useGoogleMaps.js */
export default function () {
  return {
    getPlaceDetails: vi.fn().mockResolvedValue({
      street_address: '123 Main St',
      extended_address: 'ap 802',
      city: 'New York',
      state: 'NY',
      postal_code: '10001',
      country: 'US',
    }),
    getPlacePredictions: () => {
      return Promise.resolve([{ id: '1', name: '123 Main St, ap 802, New York, US' }]);
    },
  };
}

/* tests/setup-env.ts */
import '@leaflink/dom-testing-utils/setup-env'; // to ensure lodash-es/debounce is mocked properly
vi.mock('@leaflink/stash/useGoogleMaps');

/* src/components/YourComponent.spec.ts */
const user = userEvent.setup();
// Start typing in the AddressSelect select input to trigger the useGoogleMaps mock response
await user.type(screen.getByPlaceholderText('Search'), 'type anything');
// The mock response will be used to populate the options - so it will always be the same
// (id of 1 from the getPlacePredictions mock)
await user.selectOptions(screen.getByLabelText('Bank address'), ['1']);

It's also encouraged the use of @leaflink/dom-testing-utils for testing utilities like global and local test setup, mocking endpoints, clean up components, get selected options and more. Checkout the documention for learning more about this package.

Assets

When using Stash, a collection of assets are available to use, such as icons and illustrations.

In order to configure the assets path for your project, you can do it via the staticPath option. By default, this property is set to the /assets path.

import { createApp } from 'vue';
import stash from '@leaflink/stash';

const app = createApp(App);

app.use(stash, {
  staticPath: '/my-assets-path',
});

Usually you will want to copy assets from the package installed in your node_modules folder to your application.

For projects using Vite, you can do it using the copy rollup plugin and adding to your plugins array:

npm install -D rollup-plugin-copy
import path from 'node:path';

import vue from '@vitejs/plugin-vue';
import copy from 'rollup-plugin-copy';
import { defineConfig, loadEnv } from 'vite';

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), '');

  return {
    plugins: [
      vue(),
      copy({
        targets: [
          {
            src: path.resolve(__dirname, 'node_modules/@leaflink/stash/assets/spritesheet.svg'),
            dest: 'public/static',
          },
          {
            src: path.resolve(__dirname, 'node_modules/@leaflink/stash/assets/illustrations'),
            dest: 'public/static',
          },
        ],
        hook: 'buildStart',
      }),
    ],
  };
});

Illustrations and Icons

It's encouraged to use Stash's Illustration and Icon components for these kind of data.

  1. If your work includes a new illustration, add it here in Stash: https://github.com/LeafLink/stash/tree/main/assets/illustrations
  2. Import the component:
    import Illustration from '@leaflink/stash/Illustration.vue';
    import Icon from '@leaflink/stash/Icon.vue';
    
  3. Use it in your template:
    <Illustration name="your-illustration-name" /> <Icon name="your-icon-name" />
    
  4. Customize however you like: i.e:
    <Illustration name="your-illustration-name" :size="58" /> <Icon name="your-icon-name" :size="58" />
    

If you're working on existing templates that use SvgIcon using one of the newer illustrations and you feel inclined to migrate it over to Stash, that would be helpful!

Testing Icon's and Illustration's

The Icon and Illustration components from Stash now loads SVG's asyncronously. This is fine for tests unless you're actually looking to query for an SVG. In that event, you will just need to be sure to await findBy... the icon before asserting on or interacting with it.

Example

<!-- SomeComponent.vue -->
<Icon v-if="someCondition" data-test="delete-adjustment-icon" name="trashcan" />
// ❌ Fails
renderAccountingAmounts();
expect(screen.getByTestId('delete-adjustment-icon')).toBeInTheDocument();

// ❌ Possible false-positives
renderAccountingAmounts();
expect(screen.queryByTestId('delete-adjustment-icon')).not.toBeInTheDocument();

// ✅ Passes
renderAccountingAmounts();
expect(await screen.findByTestId('delete-adjustment-icon')).toBeInTheDocument();

// ✅ Passes
import { flushPromises } from '@vue/test-utils';

renderAccountingAmounts();
await flushPromises();
expect(screen.queryByTestId('delete-adjustment-icon')).not.toBeInTheDocument();

Details

Contributing

Anyone can contribute to @leaflink/stash! Please check out the Contribution guide for guidelines about how to proceed.

Reach out in slack if you have other questions.

Architecture

If you are wanting to understand the how or why behind what is built, see the ARCHITECTURE.md doc.

FAQs

Package last updated on 15 Nov 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc