Skip to content

Repository files navigation

@joyautomation/salt

A Svelte 5 component library with theming, forms, icons, and notifications.

Installation

pnpm add @joyautomation/salt

Import the styles in your root layout or app entry point:

<script>
  import '@joyautomation/salt/styles.scss'
</script>

Theming

Salt includes a light/dark theme system using CSS custom variables. Apply the default theme class to <body> in your app.html:

<body class="themeLight" data-sveltekit-preload-data="hover">
  %sveltekit.body%
</body>

ThemeButton

A toggle button that switches between light and dark mode via a SvelteKit form action.

<script>
  import { ThemeButton } from '@joyautomation/salt'
</script>

<ThemeButton theme="themeLight" />

Wire up the server action in your +layout.server.ts:

import { actions as saltActions } from '@joyautomation/salt'
import type { Actions } from '@sveltejs/kit'

export const actions: Actions = {
  setTheme: saltActions.setTheme
}

ThemeSwitch

A three-option selector (System / Light / Dark):

<script>
  import { ThemeSwitch } from '@joyautomation/salt'
</script>

<ThemeSwitch />

Theme State

Programmatic access to the current theme:

import { themeState, getEffectiveTheme } from '@joyautomation/salt'

// Read current theme
themeState.value // 'themeSystem' | 'themeLight' | 'themeDark'

// Get resolved theme (resolves 'themeSystem' to actual preference)
getEffectiveTheme() // 'themeLight' | 'themeDark'

// Set theme programmatically
themeState.set('themeDark')

// Initialize from server cookie
themeState.initialize(serverTheme)

Toast Notifications

Drop-in toast notification system that auto-displays messages returned from SvelteKit form actions.

Setup

Add the Toast component to your root layout:

<script>
  import { Toast } from '@joyautomation/salt'
</script>

<slot />
<Toast />

Triggering Toasts from Form Actions

Return an object with message and type from any form action:

// +page.server.ts
export const actions = {
  save: async () => {
    return {
      message: 'Changes saved successfully',
      type: 'success' // 'success' | 'error' | 'warning'
    }
  }
}

Triggering Toasts Programmatically

import { state } from '@joyautomation/salt'

state.addNotification({
  message: 'Something happened',
  type: 'warning'
})

Notifications auto-dismiss after 5 seconds.

Forms

A JSON-driven form system using a 2D array layout where each inner array represents a row of fields.

Basic Usage

<script>
  import { forms } from '@joyautomation/salt'
  import type { FormInputs } from '@joyautomation/salt'

  const inputs: FormInputs = $state([
    // Row 1: two fields side by side
    [
      {
        id: 'name',
        name: 'name',
        label: 'Name',
        type: 'text',
        value: '',
        validations: [[(v) => v.trim().length === 0, 'Name is required']]
      },
      {
        id: 'email',
        name: 'email',
        label: 'Email',
        type: 'email',
        value: '',
        validations: [
          [(v) => v.trim().length === 0, 'Email is required'],
          [(v) => !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), 'Enter a valid email']
        ]
      }
    ],
    // Row 2: select dropdown
    [
      {
        id: 'role',
        name: 'role',
        label: 'Role',
        type: 'select',
        value: 'user',
        validations: [],
        options: [
          { value: 'user', label: 'User' },
          { value: 'admin', label: 'Admin' }
        ]
      }
    ],
    // Row 3: textarea
    [
      {
        id: 'notes',
        name: 'notes',
        label: 'Notes',
        type: 'textarea',
        value: '',
        validations: []
      }
    ]
  ])
</script>

<forms.Form inputs={inputs} action="?/save" buttonText="Save" />

Field Types

Set type on an InputProps entry:

  • Text inputs: text, password, email, number, tel, datetime-local, etc.
  • Textarea: textarea — renders a resizable text area
  • Select: select — renders a dropdown, requires options: { value: string, label: string }[]

Validation

Each field has a validations array of [testFn, errorMessage] tuples. The test function returns true when the value is invalid:

validations: [
  [(value) => value.length === 0, 'Required'],
  [(value) => value.length < 3, 'Must be at least 3 characters'],
  // Cross-field validation — second param gives access to all form inputs
  [(value, inputs) => {
    const other = inputs.flat().find(i => i.name === 'password')
    return value !== other?.value
  }, 'Passwords must match']
]

The submit button is disabled until all validations pass.

Form Component

<forms.Form
  inputs={formInputs}
  action="?/submit"
  buttonText="Submit"
  onsubmitstart={() => console.log('submitting...')}
  onsubmitend={() => console.log('done')}
/>

Props:

  • inputsFormInputs (2D array of InputProps)
  • action — SvelteKit form action path
  • buttonText — submit button label (default: 'Submit')
  • onsubmitstart / onsubmitend — optional callbacks

Individual Components

You can also use form components standalone:

<forms.Input
  id="name"
  name="name"
  label="Name"
  type="text"
  bind:value={name}
  validations={[[(v) => v.length === 0, 'Required']]}
  touched={true}
/>

<forms.Select
  id="color"
  name="color"
  label="Color"
  bind:value={color}
  validations={[]}
  options={[{ value: 'red', label: 'Red' }, { value: 'blue', label: 'Blue' }]}
/>

SearchableSelect

A filterable dropdown with keyboard navigation, independent of the form system:

<script>
  import { forms } from '@joyautomation/salt'

  let selected = $state('')
</script>

<forms.SearchableSelect
  options={[
    { value: 'svelte', label: 'Svelte', sublabel: 'Cybernetically enhanced' },
    { value: 'react', label: 'React', sublabel: 'A JavaScript library' }
  ]}
  placeholder="Choose a framework..."
  onSelect={(option) => (selected = option.value)}
  disabled={false}
/>

Types

import type { InputProps, FormInputs, FormInputsPartial } from '@joyautomation/salt'

// InputProps — single field configuration
// FormInputs — InputProps[][] (2D array, each inner array is a row)
// FormInputsPartial — partial version for merging/patching field configs

Icons

All 648 Heroicons (324 outline + 324 solid) are available as tree-shakeable Svelte components.

Usage

<script>
  // Outline icons (default export)
  import { AcademicCap, ArrowPath, Trash } from '@joyautomation/salt/icons'

  // Style-specific imports
  import { AcademicCap } from '@joyautomation/salt/icons/outline'
  import { AcademicCap } from '@joyautomation/salt/icons/solid'
</script>

<!-- Default size is 1.5rem -->
<AcademicCap />

<!-- Custom size -->
<ArrowPath size="2rem" />
<Trash size="1rem" />

Icons inherit currentColor so they match the surrounding text color.

Regenerating Icons

If you update the SVGs in src/lib/components/icons/svg/, regenerate with:

npm run generate:icons

Toggle

A toggle switch component with hidden form input:

<script>
  import { Toggle } from '@joyautomation/salt'
</script>

<Toggle id="notifications" name="notifications" checked={true} />

Props:

  • id — input ID
  • name — form field name
  • checked — boolean state
  • buttonType'button' (default) or 'submit'
  • selector / selectorName — optional additional hidden input

Development

pnpm install
pnpm dev          # starts dev server on port 3014
pnpm run check    # type check
pnpm run package  # build the library

Publishing

Create a GitHub release with a tag like v0.2.0. The CI workflow will automatically set the version, build, and publish to npm.

About

Joy Automation

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages