@joyautomation/salt
v0.1.5
Published
A Svelte 5 component library with theming, forms, icons, and notifications.
Downloads
724
Readme
@joyautomation/salt
A Svelte 5 component library with theming, forms, icons, and notifications.
Installation
pnpm add @joyautomation/saltImport 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, requiresoptions: { 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:
inputs—FormInputs(2D array ofInputProps)action— SvelteKit form action pathbuttonText— 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 configsIcons
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:iconsToggle
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 IDname— form field namechecked— boolean statebuttonType—'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 libraryPublishing
Create a GitHub release with a tag like v0.2.0. The CI workflow will automatically set the version, build, and publish to npm.
