@dillingerstaffing/strand-svelte 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/dist/css/strand-ui.css +2583 -0
- package/dist/index.js +4154 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
- package/src/components/Alert/Alert.svelte +32 -0
- package/src/components/Alert/Alert.test.ts +64 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/Avatar/Avatar.svelte +40 -0
- package/src/components/Avatar/Avatar.test.ts +55 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Badge/Badge.svelte +41 -0
- package/src/components/Badge/Badge.test.ts +55 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Breadcrumb/Breadcrumb.svelte +29 -0
- package/src/components/Breadcrumb/Breadcrumb.test.ts +66 -0
- package/src/components/Breadcrumb/index.ts +2 -0
- package/src/components/Button/Button.svelte +55 -0
- package/src/components/Button/Button.test.ts +110 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Card/Card.svelte +17 -0
- package/src/components/Card/Card.test.ts +32 -0
- package/src/components/Card/index.ts +2 -0
- package/src/components/Checkbox/Checkbox.svelte +62 -0
- package/src/components/Checkbox/Checkbox.test.ts +67 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/CodeBlock/CodeBlock.svelte +14 -0
- package/src/components/CodeBlock/CodeBlock.test.ts +36 -0
- package/src/components/CodeBlock/index.ts +2 -0
- package/src/components/Container/Container.svelte +14 -0
- package/src/components/Container/Container.test.ts +23 -0
- package/src/components/Container/index.ts +2 -0
- package/src/components/DataReadout/DataReadout.svelte +19 -0
- package/src/components/DataReadout/DataReadout.test.ts +35 -0
- package/src/components/DataReadout/index.ts +2 -0
- package/src/components/Dialog/Dialog.svelte +131 -0
- package/src/components/Dialog/Dialog.test.ts +77 -0
- package/src/components/Dialog/index.ts +2 -0
- package/src/components/Divider/Divider.svelte +36 -0
- package/src/components/Divider/Divider.test.ts +34 -0
- package/src/components/Divider/index.ts +2 -0
- package/src/components/FormField/FormField.svelte +39 -0
- package/src/components/FormField/FormField.test.ts +58 -0
- package/src/components/FormField/index.ts +2 -0
- package/src/components/Grid/Grid.svelte +13 -0
- package/src/components/Grid/Grid.test.ts +32 -0
- package/src/components/Grid/index.ts +2 -0
- package/src/components/Input/Input.svelte +41 -0
- package/src/components/Input/Input.test.ts +64 -0
- package/src/components/Input/index.ts +2 -0
- package/src/components/Link/Link.svelte +17 -0
- package/src/components/Link/Link.test.ts +28 -0
- package/src/components/Link/index.ts +2 -0
- package/src/components/Nav/Nav.svelte +69 -0
- package/src/components/Nav/Nav.test.ts +75 -0
- package/src/components/Nav/index.ts +2 -0
- package/src/components/Progress/Progress.svelte +78 -0
- package/src/components/Progress/Progress.test.ts +58 -0
- package/src/components/Progress/index.ts +2 -0
- package/src/components/Radio/Radio.svelte +46 -0
- package/src/components/Radio/Radio.test.ts +52 -0
- package/src/components/Radio/index.ts +2 -0
- package/src/components/Section/Section.svelte +17 -0
- package/src/components/Section/Section.test.ts +29 -0
- package/src/components/Section/index.ts +2 -0
- package/src/components/Select/Select.svelte +45 -0
- package/src/components/Select/Select.test.ts +59 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/Skeleton/Skeleton.svelte +25 -0
- package/src/components/Skeleton/Skeleton.test.ts +44 -0
- package/src/components/Skeleton/index.ts +2 -0
- package/src/components/Slider/Slider.svelte +37 -0
- package/src/components/Slider/Slider.test.ts +45 -0
- package/src/components/Slider/index.ts +2 -0
- package/src/components/Spinner/Spinner.svelte +15 -0
- package/src/components/Spinner/Spinner.test.ts +38 -0
- package/src/components/Spinner/index.ts +2 -0
- package/src/components/Stack/Stack.svelte +27 -0
- package/src/components/Stack/Stack.test.ts +46 -0
- package/src/components/Stack/index.ts +2 -0
- package/src/components/Switch/Switch.svelte +48 -0
- package/src/components/Switch/Switch.test.ts +61 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Table/Table.svelte +67 -0
- package/src/components/Table/Table.test.ts +88 -0
- package/src/components/Table/index.ts +2 -0
- package/src/components/Tabs/Tabs.svelte +89 -0
- package/src/components/Tabs/Tabs.test.ts +66 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Tag/Tag.svelte +33 -0
- package/src/components/Tag/Tag.test.ts +63 -0
- package/src/components/Tag/index.ts +2 -0
- package/src/components/Textarea/Textarea.svelte +53 -0
- package/src/components/Textarea/Textarea.test.ts +53 -0
- package/src/components/Textarea/index.ts +2 -0
- package/src/components/Toast/Toast.svelte +29 -0
- package/src/components/Toast/Toast.test.ts +60 -0
- package/src/components/Toast/ToastProvider.svelte +45 -0
- package/src/components/Toast/index.ts +5 -0
- package/src/components/Toast/useToast.ts +78 -0
- package/src/components/Tooltip/Tooltip.svelte +56 -0
- package/src/components/Tooltip/Tooltip.test.ts +50 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/index.ts +46 -0
- package/src/test-setup.ts +7 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*! Strand Svelte | MIT License | dillingerstaffing.com */
|
|
2
|
+
|
|
3
|
+
import { getContext, setContext } from 'svelte'
|
|
4
|
+
import { writable } from 'svelte/store'
|
|
5
|
+
import type { Writable } from 'svelte/store'
|
|
6
|
+
|
|
7
|
+
export type ToastStatus = 'info' | 'success' | 'warning' | 'error'
|
|
8
|
+
|
|
9
|
+
export interface ToastOptions {
|
|
10
|
+
message: string
|
|
11
|
+
status?: ToastStatus
|
|
12
|
+
duration?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ToastEntry {
|
|
16
|
+
id: number
|
|
17
|
+
message: string
|
|
18
|
+
status: ToastStatus
|
|
19
|
+
duration: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ToastContextValue {
|
|
23
|
+
toasts: Writable<ToastEntry[]>
|
|
24
|
+
toast: (options: ToastOptions) => void
|
|
25
|
+
removeToast: (id: number) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const TOAST_KEY = Symbol('StrandToast')
|
|
29
|
+
|
|
30
|
+
let toastIdCounter = 0
|
|
31
|
+
|
|
32
|
+
export function createToastContext(): ToastContextValue {
|
|
33
|
+
const toasts = writable<ToastEntry[]>([])
|
|
34
|
+
const timers = new Map<number, ReturnType<typeof setTimeout>>()
|
|
35
|
+
|
|
36
|
+
function removeToast(id: number) {
|
|
37
|
+
const timer = timers.get(id)
|
|
38
|
+
if (timer !== undefined) {
|
|
39
|
+
clearTimeout(timer)
|
|
40
|
+
timers.delete(id)
|
|
41
|
+
}
|
|
42
|
+
toasts.update((prev) => prev.filter((t) => t.id !== id))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function addToast(options: ToastOptions) {
|
|
46
|
+
const entry: ToastEntry = {
|
|
47
|
+
id: ++toastIdCounter,
|
|
48
|
+
message: options.message,
|
|
49
|
+
status: options.status ?? 'info',
|
|
50
|
+
duration: options.duration ?? 5000,
|
|
51
|
+
}
|
|
52
|
+
toasts.update((prev) => [...prev, entry])
|
|
53
|
+
|
|
54
|
+
if (entry.duration > 0) {
|
|
55
|
+
const timer = setTimeout(() => {
|
|
56
|
+
removeToast(entry.id)
|
|
57
|
+
}, entry.duration)
|
|
58
|
+
timers.set(entry.id, timer)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const ctx: ToastContextValue = {
|
|
63
|
+
toasts,
|
|
64
|
+
toast: addToast,
|
|
65
|
+
removeToast,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setContext(TOAST_KEY, ctx)
|
|
69
|
+
return ctx
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getToastContext(): ToastContextValue {
|
|
73
|
+
const ctx = getContext<ToastContextValue>(TOAST_KEY)
|
|
74
|
+
if (!ctx) {
|
|
75
|
+
throw new Error('getToastContext must be used within a ToastProvider')
|
|
76
|
+
}
|
|
77
|
+
return ctx
|
|
78
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<!--! Strand Svelte | MIT License | dillingerstaffing.com -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
/** Tooltip text */
|
|
4
|
+
export let content: string
|
|
5
|
+
/** Position relative to trigger */
|
|
6
|
+
export let position: 'top' | 'right' | 'bottom' | 'left' = 'top'
|
|
7
|
+
/** Delay in ms before showing */
|
|
8
|
+
export let delay: number = 200
|
|
9
|
+
|
|
10
|
+
let visible = false
|
|
11
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
12
|
+
|
|
13
|
+
let tooltipIdCounter = 0
|
|
14
|
+
const tooltipId = `strand-tooltip-${++tooltipIdCounter}`
|
|
15
|
+
|
|
16
|
+
function show() {
|
|
17
|
+
timer = setTimeout(() => {
|
|
18
|
+
visible = true
|
|
19
|
+
}, delay)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function hide() {
|
|
23
|
+
if (timer !== null) {
|
|
24
|
+
clearTimeout(timer)
|
|
25
|
+
timer = null
|
|
26
|
+
}
|
|
27
|
+
visible = false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
$: tooltipClasses = [
|
|
31
|
+
'strand-tooltip',
|
|
32
|
+
`strand-tooltip--${position}`,
|
|
33
|
+
visible && 'strand-tooltip--visible',
|
|
34
|
+
].filter(Boolean).join(' ')
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
38
|
+
<span
|
|
39
|
+
class="strand-tooltip__wrapper"
|
|
40
|
+
on:mouseenter={show}
|
|
41
|
+
on:mouseleave={hide}
|
|
42
|
+
on:focus={show}
|
|
43
|
+
on:blur={hide}
|
|
44
|
+
aria-describedby={tooltipId}
|
|
45
|
+
{...$$restProps}
|
|
46
|
+
>
|
|
47
|
+
<slot />
|
|
48
|
+
<span
|
|
49
|
+
id={tooltipId}
|
|
50
|
+
class={tooltipClasses}
|
|
51
|
+
role="tooltip"
|
|
52
|
+
aria-hidden={!visible}
|
|
53
|
+
>
|
|
54
|
+
{content}
|
|
55
|
+
</span>
|
|
56
|
+
</span>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*! Strand Svelte | MIT License | dillingerstaffing.com */
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect } from 'vitest'
|
|
4
|
+
import { render } from '@testing-library/svelte'
|
|
5
|
+
import Tooltip from './Tooltip.svelte'
|
|
6
|
+
|
|
7
|
+
describe('Tooltip', () => {
|
|
8
|
+
it('renders wrapper with aria-describedby', () => {
|
|
9
|
+
const { container } = render(Tooltip, { props: { content: 'Help text' } })
|
|
10
|
+
const wrapper = container.querySelector('.strand-tooltip__wrapper')
|
|
11
|
+
expect(wrapper).toBeInTheDocument()
|
|
12
|
+
expect(wrapper).toHaveAttribute('aria-describedby')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('renders tooltip element with role', () => {
|
|
16
|
+
const { container } = render(Tooltip, { props: { content: 'Help text' } })
|
|
17
|
+
const tip = container.querySelector('[role="tooltip"]')
|
|
18
|
+
expect(tip).toBeInTheDocument()
|
|
19
|
+
expect(tip).toHaveTextContent('Help text')
|
|
20
|
+
expect(tip).toHaveClass('strand-tooltip')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('applies position class', () => {
|
|
24
|
+
const positions = ['top', 'right', 'bottom', 'left'] as const
|
|
25
|
+
for (const position of positions) {
|
|
26
|
+
const { container, unmount } = render(Tooltip, { props: { content: 'Tip', position } })
|
|
27
|
+
expect(container.querySelector('.strand-tooltip')).toHaveClass(`strand-tooltip--${position}`)
|
|
28
|
+
unmount()
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('is hidden by default', () => {
|
|
33
|
+
const { container } = render(Tooltip, { props: { content: 'Tip' } })
|
|
34
|
+
const tip = container.querySelector('.strand-tooltip')
|
|
35
|
+
expect(tip).toHaveAttribute('aria-hidden', 'true')
|
|
36
|
+
expect(tip).not.toHaveClass('strand-tooltip--visible')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('tooltip id matches aria-describedby', () => {
|
|
40
|
+
const { container } = render(Tooltip, { props: { content: 'Tip' } })
|
|
41
|
+
const wrapper = container.querySelector('.strand-tooltip__wrapper')
|
|
42
|
+
const tip = container.querySelector('[role="tooltip"]')
|
|
43
|
+
expect(wrapper!.getAttribute('aria-describedby')).toBe(tip!.id)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('defaults to top position', () => {
|
|
47
|
+
const { container } = render(Tooltip, { props: { content: 'Tip' } })
|
|
48
|
+
expect(container.querySelector('.strand-tooltip')).toHaveClass('strand-tooltip--top')
|
|
49
|
+
})
|
|
50
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*! Strand Svelte | MIT License | dillingerstaffing.com */
|
|
2
|
+
|
|
3
|
+
// Input
|
|
4
|
+
export { default as Button } from './components/Button/Button.svelte'
|
|
5
|
+
export { default as Input } from './components/Input/Input.svelte'
|
|
6
|
+
export { default as Textarea } from './components/Textarea/Textarea.svelte'
|
|
7
|
+
export { default as Select } from './components/Select/Select.svelte'
|
|
8
|
+
export { default as Checkbox } from './components/Checkbox/Checkbox.svelte'
|
|
9
|
+
export { default as Radio } from './components/Radio/Radio.svelte'
|
|
10
|
+
export { default as Switch } from './components/Switch/Switch.svelte'
|
|
11
|
+
export { default as Slider } from './components/Slider/Slider.svelte'
|
|
12
|
+
export { default as FormField } from './components/FormField/FormField.svelte'
|
|
13
|
+
|
|
14
|
+
// Display
|
|
15
|
+
export { default as Card } from './components/Card/Card.svelte'
|
|
16
|
+
export { default as Badge } from './components/Badge/Badge.svelte'
|
|
17
|
+
export { default as Avatar } from './components/Avatar/Avatar.svelte'
|
|
18
|
+
export { default as Tag } from './components/Tag/Tag.svelte'
|
|
19
|
+
export { default as Table } from './components/Table/Table.svelte'
|
|
20
|
+
export { default as DataReadout } from './components/DataReadout/DataReadout.svelte'
|
|
21
|
+
export { default as CodeBlock } from './components/CodeBlock/CodeBlock.svelte'
|
|
22
|
+
|
|
23
|
+
// Layout
|
|
24
|
+
export { default as Stack } from './components/Stack/Stack.svelte'
|
|
25
|
+
export { default as Grid } from './components/Grid/Grid.svelte'
|
|
26
|
+
export { default as Container } from './components/Container/Container.svelte'
|
|
27
|
+
export { default as Divider } from './components/Divider/Divider.svelte'
|
|
28
|
+
export { default as Section } from './components/Section/Section.svelte'
|
|
29
|
+
|
|
30
|
+
// Navigation
|
|
31
|
+
export { default as Link } from './components/Link/Link.svelte'
|
|
32
|
+
export { default as Tabs } from './components/Tabs/Tabs.svelte'
|
|
33
|
+
export { default as Breadcrumb } from './components/Breadcrumb/Breadcrumb.svelte'
|
|
34
|
+
export { default as Nav } from './components/Nav/Nav.svelte'
|
|
35
|
+
|
|
36
|
+
// Feedback
|
|
37
|
+
export { default as Toast } from './components/Toast/Toast.svelte'
|
|
38
|
+
export { default as ToastProvider } from './components/Toast/ToastProvider.svelte'
|
|
39
|
+
export { createToastContext, getToastContext } from './components/Toast/useToast'
|
|
40
|
+
export type { ToastStatus, ToastOptions, ToastEntry, ToastContextValue } from './components/Toast/useToast'
|
|
41
|
+
export { default as Alert } from './components/Alert/Alert.svelte'
|
|
42
|
+
export { default as Dialog } from './components/Dialog/Dialog.svelte'
|
|
43
|
+
export { default as Tooltip } from './components/Tooltip/Tooltip.svelte'
|
|
44
|
+
export { default as Progress } from './components/Progress/Progress.svelte'
|
|
45
|
+
export { default as Spinner } from './components/Spinner/Spinner.svelte'
|
|
46
|
+
export { default as Skeleton } from './components/Skeleton/Skeleton.svelte'
|