@human-synthesis/norns-ui 0.0.3 → 0.0.6
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/LICENSE +21 -0
- package/README.md +81 -225
- package/package.json +13 -3
- package/src/auto-import.js +81 -19
- package/src/components/Accordion.n +39 -0
- package/src/components/Audio.n +24 -0
- package/src/components/Autocomplete.n +134 -0
- package/src/components/Avatar.n +27 -0
- package/src/components/AvatarGroup.n +25 -0
- package/src/components/Badge.n +20 -0
- package/src/components/Banner.n +25 -0
- package/src/components/Breadcrumbs.n +22 -0
- package/src/components/Btn.n +29 -8
- package/src/components/ButtonGroup.n +14 -0
- package/src/components/Calendar.n +153 -0
- package/src/components/Card.n +34 -0
- package/src/components/Carousel.n +66 -0
- package/src/components/Chip.n +27 -0
- package/src/components/Collapsible.n +29 -0
- package/src/components/ColorPicker.n +62 -0
- package/src/components/ContextMenu.n +67 -0
- package/src/components/CopyButton.n +50 -0
- package/src/components/DataTable.n +61 -0
- package/src/components/DatePicker.n +56 -0
- package/src/components/DateRangePicker.n +47 -0
- package/src/components/Dialog.n +35 -22
- package/src/components/Dropdown.n +54 -23
- package/src/components/GradientText.n +21 -0
- package/src/components/Header.n +23 -0
- package/src/components/HeroBanner.n +37 -0
- package/src/components/HierarchicalMenu.n +38 -0
- package/src/components/Icon.n +16 -0
- package/src/components/Image.n +31 -0
- package/src/components/MegaMenu.n +36 -0
- package/src/components/MultiSelect.n +158 -0
- package/src/components/NumberInput.n +49 -0
- package/src/components/OtpField.n +67 -0
- package/src/components/Pagination.n +73 -0
- package/src/components/Popover.n +30 -15
- package/src/components/Progress.n +26 -0
- package/src/components/ProgressCircular.n +52 -0
- package/src/components/RippleButton.n +56 -0
- package/src/components/ScrollArea.n +20 -0
- package/src/components/Separator.n +12 -0
- package/src/components/Sheet.n +36 -22
- package/src/components/ShinyButton.n +36 -0
- package/src/components/Skeleton.n +27 -0
- package/src/components/Stepper.n +35 -0
- package/src/components/Surface.n +10 -0
- package/src/components/Tabs.n +39 -13
- package/src/components/TagsInput.n +66 -0
- package/src/components/ThemeToggler.n +43 -0
- package/src/components/TimePicker.n +153 -0
- package/src/components/Timeline.n +27 -0
- package/src/components/ToggleButton.n +35 -0
- package/src/components/ToggleButtonGroup.n +54 -0
- package/src/components/Toolbar.n +15 -0
- package/src/components/Tooltip.n +52 -19
- package/src/components/Tree.n +57 -0
- package/src/components/Uploader.n +88 -0
- package/src/components/Video.n +26 -0
- package/src/index.js +64 -1
- package/src/lib/behaviors/clickOutside.svelte.js +54 -0
- package/src/lib/behaviors/escape.svelte.js +36 -0
- package/src/lib/behaviors/floating.svelte.js +137 -0
- package/src/lib/behaviors/focusTrap.svelte.js +80 -0
- package/src/lib/behaviors/index.js +14 -0
- package/src/lib/behaviors/portal.svelte.js +32 -0
- package/src/lib/behaviors/rovingTabindex.svelte.js +76 -0
- package/src/lib/behaviors/scrollLock.svelte.js +56 -0
- package/src/motion/AnimatedNumber.n +55 -0
- package/src/motion/GradientBackground.n +25 -0
- package/src/motion/LiquidButton.n +48 -0
- package/src/motion/Reveal.n +37 -0
- package/src/motion/Sparkles.n +53 -0
- package/src/motion/index.js +18 -0
- package/src/styles/atoms.css +1481 -223
- package/src/styles/tokens.css +204 -40
- package/src/types/Accordion.d.ts +17 -0
- package/src/types/AnimatedNumber.d.ts +15 -0
- package/src/types/Audio.d.ts +18 -0
- package/src/types/Autocomplete.d.ts +17 -0
- package/src/types/Avatar.d.ts +14 -0
- package/src/types/AvatarGroup.d.ts +15 -0
- package/src/types/Badge.d.ts +14 -0
- package/src/types/Banner.d.ts +15 -0
- package/src/types/Breadcrumbs.d.ts +15 -0
- package/src/types/Btn.d.ts +10 -0
- package/src/types/ButtonGroup.d.ts +10 -0
- package/src/types/Calendar.d.ts +20 -0
- package/src/types/Card.d.ts +19 -0
- package/src/types/Carousel.d.ts +23 -0
- package/src/types/Chip.d.ts +12 -0
- package/src/types/Collapsible.d.ts +13 -0
- package/src/types/ColorPicker.d.ts +15 -0
- package/src/types/ContextMenu.d.ts +24 -0
- package/src/types/CopyButton.d.ts +16 -0
- package/src/types/DataTable.d.ts +27 -0
- package/src/types/DatePicker.d.ts +23 -0
- package/src/types/DateRangePicker.d.ts +17 -0
- package/src/types/Dialog.d.ts +4 -0
- package/src/types/Dropdown.d.ts +2 -1
- package/src/types/GradientBackground.d.ts +14 -0
- package/src/types/GradientText.d.ts +19 -0
- package/src/types/Header.d.ts +12 -0
- package/src/types/HeroBanner.d.ts +17 -0
- package/src/types/HierarchicalMenu.d.ts +20 -0
- package/src/types/Icon.d.ts +21 -0
- package/src/types/Image.d.ts +15 -0
- package/src/types/LiquidButton.d.ts +15 -0
- package/src/types/MegaMenu.d.ts +29 -0
- package/src/types/MultiSelect.d.ts +16 -0
- package/src/types/NumberInput.d.ts +17 -0
- package/src/types/OtpField.d.ts +13 -0
- package/src/types/Pagination.d.ts +13 -0
- package/src/types/Progress.d.ts +14 -0
- package/src/types/ProgressCircular.d.ts +14 -0
- package/src/types/Reveal.d.ts +20 -0
- package/src/types/RippleButton.d.ts +15 -0
- package/src/types/ScrollArea.d.ts +11 -0
- package/src/types/Separator.d.ts +9 -0
- package/src/types/Sheet.d.ts +3 -0
- package/src/types/ShinyButton.d.ts +15 -0
- package/src/types/Skeleton.d.ts +13 -0
- package/src/types/Sparkles.d.ts +13 -0
- package/src/types/Stepper.d.ts +19 -0
- package/src/types/Surface.d.ts +10 -0
- package/src/types/Tabs.d.ts +2 -0
- package/src/types/TagsInput.d.ts +17 -0
- package/src/types/ThemeToggler.d.ts +17 -0
- package/src/types/TimePicker.d.ts +20 -0
- package/src/types/Timeline.d.ts +17 -0
- package/src/types/ToggleButton.d.ts +16 -0
- package/src/types/ToggleButtonGroup.d.ts +21 -0
- package/src/types/Toolbar.d.ts +12 -0
- package/src/types/Tooltip.d.ts +3 -0
- package/src/types/Tree.d.ts +20 -0
- package/src/types/Uploader.d.ts +15 -0
- package/src/types/Video.d.ts +20 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
.color-picker(class!="{classes}")
|
|
2
|
+
label.color-picker-swatch(style!="{`background: ${value}`}")
|
|
3
|
+
input(
|
|
4
|
+
type="color"
|
|
5
|
+
value!="{value}"
|
|
6
|
+
oninput!="{(e) => value = e.currentTarget.value}"
|
|
7
|
+
disabled!="{disabled}"
|
|
8
|
+
class="sr-only"
|
|
9
|
+
aria-label="Pick color"
|
|
10
|
+
)
|
|
11
|
+
input.input.color-picker-text(
|
|
12
|
+
type="text"
|
|
13
|
+
value!="{value}"
|
|
14
|
+
oninput!="{onTextInput}"
|
|
15
|
+
disabled!="{disabled}"
|
|
16
|
+
spellcheck="false"
|
|
17
|
+
placeholder="#000000"
|
|
18
|
+
name!="{name}"
|
|
19
|
+
id!="{resolvedId}"
|
|
20
|
+
)
|
|
21
|
+
+if('!hideCopy')
|
|
22
|
+
button.btn.btn-ghost.btn-sm(type="button" aria-label="Copy" onclick!="{copy}")
|
|
23
|
+
Icon(name!="{copied ? 'lucide:check' : 'lucide:copy'}" size="size-3.5")
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
27
|
+
import { getContext } from 'svelte'
|
|
28
|
+
import Icon from './Icon.n'
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
value = $bindable('#7c3aed')
|
|
32
|
+
disabled = false
|
|
33
|
+
hideCopy = false
|
|
34
|
+
name
|
|
35
|
+
id: providedId
|
|
36
|
+
class: extra = ''
|
|
37
|
+
} .= $props()
|
|
38
|
+
|
|
39
|
+
field := getContext 'norns-ui:field'
|
|
40
|
+
resolvedId := providedId ?? field?.id
|
|
41
|
+
|
|
42
|
+
classes := cn 'color-picker-wrap', extra
|
|
43
|
+
|
|
44
|
+
copied .= $state false
|
|
45
|
+
|
|
46
|
+
hexRe := /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
|
47
|
+
|
|
48
|
+
onTextInput := (e) =>
|
|
49
|
+
raw := e.currentTarget.value.trim()
|
|
50
|
+
next := raw.startsWith('#') ? raw : `#${raw}`
|
|
51
|
+
if hexRe.test(next.replace('#', ''))
|
|
52
|
+
value = next.toLowerCase()
|
|
53
|
+
else
|
|
54
|
+
value = next // let user keep typing; not all keystrokes are valid
|
|
55
|
+
|
|
56
|
+
copy := =>
|
|
57
|
+
try
|
|
58
|
+
await navigator.clipboard.writeText value
|
|
59
|
+
copied = true
|
|
60
|
+
setTimeout (=> copied = false), 1200
|
|
61
|
+
catch _
|
|
62
|
+
</script>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
span(class!="{triggerClass ?? ''}" oncontextmenu!="{onContext}")
|
|
2
|
+
| {@render trigger?.()}
|
|
3
|
+
|
|
4
|
+
+if('open')
|
|
5
|
+
div(
|
|
6
|
+
use:portal
|
|
7
|
+
use:clickOutside!="{[onClose, open]}"
|
|
8
|
+
use:escape!="{[onClose, open]}"
|
|
9
|
+
use:rovingTabindex!="{{ orientation: 'vertical', selector: '[data-roving]' }}"
|
|
10
|
+
bind:this!="{menuEl}"
|
|
11
|
+
class="dropdown-content"
|
|
12
|
+
data-state="open"
|
|
13
|
+
role="menu"
|
|
14
|
+
style="position: absolute"
|
|
15
|
+
)
|
|
16
|
+
+each('items as item')
|
|
17
|
+
+if('item.separator')
|
|
18
|
+
.dropdown-separator(role="separator")
|
|
19
|
+
+if('!item.separator')
|
|
20
|
+
button.dropdown-item(
|
|
21
|
+
type="button"
|
|
22
|
+
role="menuitem"
|
|
23
|
+
data-roving
|
|
24
|
+
data-disabled!="{item.disabled ? '' : undefined}"
|
|
25
|
+
disabled!="{item.disabled}"
|
|
26
|
+
onclick!="{() => onItem(item)}"
|
|
27
|
+
)
|
|
28
|
+
+if('item.icon')
|
|
29
|
+
Icon(name!="{item.icon}" size="size-3.5" class="text-fg-muted shrink-0")
|
|
30
|
+
span.flex-1 {item.label}
|
|
31
|
+
|
|
32
|
+
<script>
|
|
33
|
+
import { positionAt } from '@human-synthesis/norns-ui/behaviors/floating.svelte.js'
|
|
34
|
+
import { portal } from '@human-synthesis/norns-ui/behaviors/portal.svelte.js'
|
|
35
|
+
import { clickOutside } from '@human-synthesis/norns-ui/behaviors/clickOutside.svelte.js'
|
|
36
|
+
import { escape } from '@human-synthesis/norns-ui/behaviors/escape.svelte.js'
|
|
37
|
+
import { rovingTabindex } from '@human-synthesis/norns-ui/behaviors/rovingTabindex.svelte.js'
|
|
38
|
+
import Icon from './Icon.n'
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
items = []
|
|
42
|
+
trigger
|
|
43
|
+
triggerClass
|
|
44
|
+
} := $props()
|
|
45
|
+
|
|
46
|
+
open .= $state false
|
|
47
|
+
menuEl .= $state null
|
|
48
|
+
clickX .= 0
|
|
49
|
+
clickY .= 0
|
|
50
|
+
|
|
51
|
+
onContext := (e) =>
|
|
52
|
+
e.preventDefault()
|
|
53
|
+
clickX = e.clientX
|
|
54
|
+
clickY = e.clientY
|
|
55
|
+
open = true
|
|
56
|
+
|
|
57
|
+
$effect =>
|
|
58
|
+
return unless open && menuEl
|
|
59
|
+
positionAt menuEl, clickX, clickY, { placement: 'right-start' }
|
|
60
|
+
|
|
61
|
+
onClose := => open = false
|
|
62
|
+
|
|
63
|
+
onItem := (item) =>
|
|
64
|
+
return if item.disabled
|
|
65
|
+
item.onSelect?.()
|
|
66
|
+
open = false
|
|
67
|
+
</script>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
button(
|
|
2
|
+
type="button"
|
|
3
|
+
class!="{classes}"
|
|
4
|
+
disabled!="{disabled}"
|
|
5
|
+
onclick!="{copy}"
|
|
6
|
+
aria-label!="{copied ? 'Copied' : (label ?? 'Copy to clipboard')}"
|
|
7
|
+
)
|
|
8
|
+
+if('copied')
|
|
9
|
+
Icon(name="lucide:check" size!="{iconSize}" class="copy-btn-check")
|
|
10
|
+
+if('!copied')
|
|
11
|
+
Icon(name!="{icon}" size!="{iconSize}")
|
|
12
|
+
+if('children')
|
|
13
|
+
| {@render children()}
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
17
|
+
import Icon from './Icon.n'
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
value = ''
|
|
21
|
+
variant = 'ghost'
|
|
22
|
+
size = 'md'
|
|
23
|
+
icon = 'lucide:copy'
|
|
24
|
+
disabled = false
|
|
25
|
+
label
|
|
26
|
+
oncopy
|
|
27
|
+
children
|
|
28
|
+
class: extra = ''
|
|
29
|
+
} := $props()
|
|
30
|
+
|
|
31
|
+
copied .= $state false
|
|
32
|
+
|
|
33
|
+
iconSize := size === 'sm' ? 'size-3.5' : size === 'lg' ? 'size-5' : 'size-4'
|
|
34
|
+
|
|
35
|
+
classes := cn
|
|
36
|
+
'btn'
|
|
37
|
+
`btn-${variant}`
|
|
38
|
+
size !== 'md' && `btn-${size}`
|
|
39
|
+
'copy-btn'
|
|
40
|
+
copied && 'copy-btn-copied'
|
|
41
|
+
extra
|
|
42
|
+
|
|
43
|
+
copy := =>
|
|
44
|
+
try
|
|
45
|
+
await navigator.clipboard.writeText String(value)
|
|
46
|
+
copied = true
|
|
47
|
+
oncopy?.(value)
|
|
48
|
+
setTimeout (=> copied = false), 1400
|
|
49
|
+
catch _
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
.data-table-wrap(class!="{wrapClasses}")
|
|
2
|
+
table.data-table
|
|
3
|
+
thead
|
|
4
|
+
tr
|
|
5
|
+
+each('columns as col')
|
|
6
|
+
th(
|
|
7
|
+
class!="{col.class ?? ''}"
|
|
8
|
+
style!="{col.width ? `width: ${col.width}` : undefined}"
|
|
9
|
+
)
|
|
10
|
+
+if('col.sortable')
|
|
11
|
+
button.data-table-sort(type="button" onclick!="{() => onSort(col.key)}")
|
|
12
|
+
span {col.label ?? col.key}
|
|
13
|
+
Icon(name!="{sortIcon(col.key)}" size="size-3.5" class="text-fg-muted")
|
|
14
|
+
+if('!col.sortable')
|
|
15
|
+
| {col.label ?? col.key}
|
|
16
|
+
tbody
|
|
17
|
+
+if('rows.length === 0')
|
|
18
|
+
tr.data-table-empty
|
|
19
|
+
td(colspan!="{columns.length}")
|
|
20
|
+
| {emptyMessage}
|
|
21
|
+
+each('rows as row, i')
|
|
22
|
+
tr(onclick!="{onrowclick ? () => onrowclick(row, i) : undefined}" class!="{onrowclick ? 'data-table-row-clickable' : ''}")
|
|
23
|
+
+each('columns as col')
|
|
24
|
+
td(class!="{col.cellClass ?? ''}")
|
|
25
|
+
| {row[col.key]}
|
|
26
|
+
|
|
27
|
+
<script>
|
|
28
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
29
|
+
import Icon from './Icon.n'
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
columns = []
|
|
33
|
+
rows = []
|
|
34
|
+
striped = false
|
|
35
|
+
dense = false
|
|
36
|
+
stickyHeader = false
|
|
37
|
+
emptyMessage = 'No data'
|
|
38
|
+
sortKey = $bindable(undefined)
|
|
39
|
+
sortDir = $bindable('asc')
|
|
40
|
+
onrowclick
|
|
41
|
+
class: extra = ''
|
|
42
|
+
} .= $props()
|
|
43
|
+
|
|
44
|
+
wrapClasses := cn
|
|
45
|
+
'data-table-root'
|
|
46
|
+
striped && 'data-table-striped'
|
|
47
|
+
dense && 'data-table-dense'
|
|
48
|
+
stickyHeader && 'data-table-sticky'
|
|
49
|
+
extra
|
|
50
|
+
|
|
51
|
+
onSort := (key) =>
|
|
52
|
+
if sortKey === key
|
|
53
|
+
sortDir = sortDir === 'asc' ? 'desc' : 'asc'
|
|
54
|
+
else
|
|
55
|
+
sortKey = key
|
|
56
|
+
sortDir = 'asc'
|
|
57
|
+
|
|
58
|
+
sortIcon := (key) =>
|
|
59
|
+
return 'lucide:chevrons-up-down' unless sortKey === key
|
|
60
|
+
sortDir === 'asc' ? 'lucide:chevron-up' : 'lucide:chevron-down'
|
|
61
|
+
</script>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Popover(bind:open class="p-3 w-[20rem]")
|
|
2
|
+
+snippet('trigger')
|
|
3
|
+
button.input.date-picker-trigger(
|
|
4
|
+
type="button"
|
|
5
|
+
disabled!="{disabled}"
|
|
6
|
+
id!="{resolvedId}"
|
|
7
|
+
aria-label!="{label ?? 'Pick a date'}"
|
|
8
|
+
class!="{hasError ? 'input-err' : ''}"
|
|
9
|
+
)
|
|
10
|
+
Icon(name="lucide:calendar" size="size-4" class="text-fg-muted shrink-0")
|
|
11
|
+
span.flex-1.text-left.truncate
|
|
12
|
+
| {value ? formatted : placeholder}
|
|
13
|
+
Icon(name="lucide:chevron-down" size="size-3.5" class="text-fg-muted shrink-0")
|
|
14
|
+
Calendar(
|
|
15
|
+
bind:value
|
|
16
|
+
min!="{min}"
|
|
17
|
+
max!="{max}"
|
|
18
|
+
onchange!="{handleChange}"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
<script>
|
|
22
|
+
import { getContext } from 'svelte'
|
|
23
|
+
import Popover from './Popover.n'
|
|
24
|
+
import Icon from './Icon.n'
|
|
25
|
+
import Calendar from './Calendar.n'
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
value = $bindable('')
|
|
29
|
+
open = $bindable(false)
|
|
30
|
+
min = ''
|
|
31
|
+
max = ''
|
|
32
|
+
placeholder = 'Pick a date'
|
|
33
|
+
disabled = false
|
|
34
|
+
closeOnSelect = true
|
|
35
|
+
label
|
|
36
|
+
name
|
|
37
|
+
id: providedId
|
|
38
|
+
error: explicitError = false
|
|
39
|
+
} .= $props()
|
|
40
|
+
|
|
41
|
+
field := getContext 'norns-ui:field'
|
|
42
|
+
resolvedId := providedId ?? field?.id
|
|
43
|
+
hasError := explicitError || field?.hasError || false
|
|
44
|
+
|
|
45
|
+
formatter := new Intl.DateTimeFormat undefined, { dateStyle: 'medium' }
|
|
46
|
+
|
|
47
|
+
formatted := $derived.by =>
|
|
48
|
+
return '' unless value
|
|
49
|
+
try
|
|
50
|
+
formatter.format new Date(`${value}T00:00:00`)
|
|
51
|
+
catch _
|
|
52
|
+
value
|
|
53
|
+
|
|
54
|
+
handleChange := =>
|
|
55
|
+
open = false if closeOnSelect
|
|
56
|
+
</script>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Popover(bind:open class="p-3 w-[20rem]")
|
|
2
|
+
+snippet('trigger')
|
|
3
|
+
button.input.date-picker-trigger(type="button" disabled!="{disabled}" aria-label="Pick a date range")
|
|
4
|
+
Icon(name="lucide:calendar-range" size="size-4" class="text-fg-muted shrink-0")
|
|
5
|
+
span.flex-1.text-left.truncate
|
|
6
|
+
| {label}
|
|
7
|
+
Icon(name="lucide:chevron-down" size="size-3.5" class="text-fg-muted shrink-0")
|
|
8
|
+
Calendar(
|
|
9
|
+
bind:rangeValue!="{value}"
|
|
10
|
+
range!="{true}"
|
|
11
|
+
min!="{min}"
|
|
12
|
+
max!="{max}"
|
|
13
|
+
)
|
|
14
|
+
.flex.justify-end.gap-2.mt-3
|
|
15
|
+
button.btn.btn-ghost.btn-sm(type="button" onclick!="{clear}") Clear
|
|
16
|
+
button.btn.btn-primary.btn-sm(type="button" onclick!="{() => open = false}") Done
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import Popover from './Popover.n'
|
|
20
|
+
import Icon from './Icon.n'
|
|
21
|
+
import Calendar from './Calendar.n'
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
value = $bindable({ start: '', end: '' })
|
|
25
|
+
open = $bindable(false)
|
|
26
|
+
min = ''
|
|
27
|
+
max = ''
|
|
28
|
+
placeholder = 'Pick a range'
|
|
29
|
+
disabled = false
|
|
30
|
+
} .= $props()
|
|
31
|
+
|
|
32
|
+
formatter := new Intl.DateTimeFormat undefined, { dateStyle: 'medium' }
|
|
33
|
+
|
|
34
|
+
fmt := (s) =>
|
|
35
|
+
return '' unless s
|
|
36
|
+
try
|
|
37
|
+
formatter.format new Date(`${s}T00:00:00`)
|
|
38
|
+
catch _
|
|
39
|
+
s
|
|
40
|
+
|
|
41
|
+
label := $derived.by =>
|
|
42
|
+
return placeholder unless value?.start
|
|
43
|
+
return `${fmt(value.start)} – …` unless value?.end
|
|
44
|
+
`${fmt(value.start)} – ${fmt(value.end)}`
|
|
45
|
+
|
|
46
|
+
clear := => value = { start: '', end: '' }
|
|
47
|
+
</script>
|
package/src/components/Dialog.n
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
+if('trigger')
|
|
2
|
+
span(class!="{triggerClass ?? ''}" onclick!="{() => open = true}")
|
|
3
|
+
| {@render trigger()}
|
|
4
|
+
|
|
5
|
+
+if('open')
|
|
6
|
+
div(use:portal use:scrollLock)
|
|
7
|
+
div(
|
|
8
|
+
data-state="open"
|
|
9
|
+
class!="{overlayClasses}"
|
|
10
|
+
onclick!="{onOverlayClick}"
|
|
11
|
+
)
|
|
12
|
+
div(
|
|
13
|
+
role="dialog"
|
|
14
|
+
aria-modal="true"
|
|
15
|
+
aria-label!="{title ?? ariaLabel}"
|
|
16
|
+
class!="{contentClasses}"
|
|
17
|
+
data-state="open"
|
|
18
|
+
use:focusTrap
|
|
19
|
+
use:escape!="{[onClose, open]}"
|
|
20
|
+
)
|
|
8
21
|
+if('title')
|
|
9
|
-
|
|
22
|
+
h2.dialog-title
|
|
10
23
|
| {title}
|
|
11
24
|
+if('description')
|
|
12
|
-
|
|
25
|
+
p.dialog-description
|
|
13
26
|
| {description}
|
|
14
27
|
+if('children')
|
|
15
28
|
.dialog-body
|
|
@@ -18,29 +31,23 @@ DialogRoot(bind:open)
|
|
|
18
31
|
.dialog-actions
|
|
19
32
|
| {@render actions()}
|
|
20
33
|
+if('!hideClose')
|
|
21
|
-
|
|
34
|
+
button.dialog-close(type="button" aria-label="Close" onclick!="{onClose}")
|
|
22
35
|
| ✕
|
|
23
36
|
|
|
24
37
|
<script>
|
|
25
|
-
import { Dialog } from 'bits-ui'
|
|
26
38
|
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
27
|
-
|
|
28
|
-
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Portal: DialogPortal
|
|
32
|
-
Overlay: DialogOverlay
|
|
33
|
-
Content: DialogContent
|
|
34
|
-
Title: DialogTitle
|
|
35
|
-
Description: DialogDescription
|
|
36
|
-
Close: DialogClose
|
|
37
|
-
} := Dialog
|
|
39
|
+
import { portal } from '@human-synthesis/norns-ui/behaviors/portal.svelte.js'
|
|
40
|
+
import { scrollLock } from '@human-synthesis/norns-ui/behaviors/scrollLock.svelte.js'
|
|
41
|
+
import { focusTrap } from '@human-synthesis/norns-ui/behaviors/focusTrap.svelte.js'
|
|
42
|
+
import { escape } from '@human-synthesis/norns-ui/behaviors/escape.svelte.js'
|
|
38
43
|
|
|
39
44
|
{
|
|
40
45
|
open = $bindable(false)
|
|
41
46
|
title
|
|
42
47
|
description
|
|
43
48
|
hideClose = false
|
|
49
|
+
dismissable = true
|
|
50
|
+
ariaLabel
|
|
44
51
|
trigger
|
|
45
52
|
actions
|
|
46
53
|
children
|
|
@@ -51,4 +58,10 @@ DialogRoot(bind:open)
|
|
|
51
58
|
|
|
52
59
|
overlayClasses := cn 'dialog-overlay', overlayClass
|
|
53
60
|
contentClasses := cn 'dialog-content', extra
|
|
61
|
+
|
|
62
|
+
onClose := => open = false
|
|
63
|
+
|
|
64
|
+
onOverlayClick := (e) =>
|
|
65
|
+
return unless dismissable
|
|
66
|
+
open = false if e.target === e.currentTarget
|
|
54
67
|
</script>
|
|
@@ -1,29 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
+if('trigger')
|
|
2
|
+
span(use:refAction bind:this!="{triggerEl}" class!="{triggerClass ?? ''}" onclick!="{toggle}")
|
|
3
|
+
| {@render trigger()}
|
|
4
|
+
|
|
5
|
+
+if('open')
|
|
6
|
+
div(
|
|
7
|
+
use:portal
|
|
8
|
+
use:floatAction
|
|
9
|
+
use:clickOutside!="{[onClose, open, triggerEl]}"
|
|
10
|
+
use:escape!="{[onClose, open]}"
|
|
11
|
+
use:rovingTabindex!="{{ orientation: 'vertical', selector: '[data-roving]' }}"
|
|
12
|
+
class!="{contentClasses}"
|
|
13
|
+
data-state="open"
|
|
14
|
+
role="menu"
|
|
15
|
+
)
|
|
16
|
+
+each('items as item, i')
|
|
17
|
+
+if('item.separator')
|
|
18
|
+
.dropdown-separator(role="separator")
|
|
19
|
+
+if('!item.separator')
|
|
20
|
+
button.dropdown-item(
|
|
21
|
+
type="button"
|
|
22
|
+
role="menuitem"
|
|
23
|
+
data-roving
|
|
24
|
+
data-disabled!="{item.disabled ? '' : undefined}"
|
|
25
|
+
disabled!="{item.disabled}"
|
|
26
|
+
onclick!="{() => onItem(item)}"
|
|
27
|
+
)
|
|
28
|
+
+if('item.icon')
|
|
29
|
+
Icon(name!="{item.icon}" size="size-3.5" class="text-fg-muted shrink-0")
|
|
30
|
+
span.flex-1 {item.label}
|
|
31
|
+
+if('children')
|
|
32
|
+
| {@render children()}
|
|
14
33
|
|
|
15
34
|
<script>
|
|
16
|
-
import { DropdownMenu } from 'bits-ui'
|
|
17
35
|
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Item: MenuItem
|
|
25
|
-
Separator: MenuSeparator
|
|
26
|
-
} := DropdownMenu
|
|
36
|
+
import { portal } from '@human-synthesis/norns-ui/behaviors/portal.svelte.js'
|
|
37
|
+
import { clickOutside } from '@human-synthesis/norns-ui/behaviors/clickOutside.svelte.js'
|
|
38
|
+
import { escape } from '@human-synthesis/norns-ui/behaviors/escape.svelte.js'
|
|
39
|
+
import { rovingTabindex } from '@human-synthesis/norns-ui/behaviors/rovingTabindex.svelte.js'
|
|
40
|
+
import { useFloating } from '@human-synthesis/norns-ui/behaviors/floating.svelte.js'
|
|
41
|
+
import Icon from './Icon.n'
|
|
27
42
|
|
|
28
43
|
{
|
|
29
44
|
open = $bindable(false)
|
|
@@ -37,5 +52,21 @@ MenuRoot(bind:open)
|
|
|
37
52
|
class: extra = ''
|
|
38
53
|
} .= $props()
|
|
39
54
|
|
|
55
|
+
placement := align === 'center' ? side : `${side}-${align}`
|
|
56
|
+
|
|
57
|
+
{ reference: refAction, floating: floatAction } := useFloating
|
|
58
|
+
placement: placement
|
|
59
|
+
offset: sideOffset
|
|
60
|
+
|
|
40
61
|
contentClasses := cn 'dropdown-content', extra
|
|
62
|
+
|
|
63
|
+
triggerEl .= $state null
|
|
64
|
+
|
|
65
|
+
toggle := => open = !open
|
|
66
|
+
onClose := => open = false
|
|
67
|
+
|
|
68
|
+
onItem := (item) =>
|
|
69
|
+
return if item.disabled
|
|
70
|
+
item.onSelect?.()
|
|
71
|
+
open = false
|
|
41
72
|
</script>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
span(class!="{classes}" style!="{styleStr}")
|
|
2
|
+
| {@render children?.()}
|
|
3
|
+
|
|
4
|
+
<script>
|
|
5
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
from = 'var(--color-primary-500)'
|
|
9
|
+
to = 'var(--color-info-500)'
|
|
10
|
+
via
|
|
11
|
+
animate = true
|
|
12
|
+
angle = 90
|
|
13
|
+
children
|
|
14
|
+
class: extra = ''
|
|
15
|
+
} := $props()
|
|
16
|
+
|
|
17
|
+
classes := cn 'gradient-text', animate && 'gradient-text-anim', extra
|
|
18
|
+
|
|
19
|
+
stops := $derived via ? `${from}, ${via}, ${to}` : `${from}, ${to}`
|
|
20
|
+
styleStr := $derived `background-image: linear-gradient(${angle}deg, ${stops})`
|
|
21
|
+
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
header(class!="{classes}")
|
|
2
|
+
.norns-header-brand
|
|
3
|
+
| {@render brand?.()}
|
|
4
|
+
+if('nav')
|
|
5
|
+
nav.norns-header-nav
|
|
6
|
+
| {@render nav()}
|
|
7
|
+
.norns-header-actions
|
|
8
|
+
| {@render actions?.()}
|
|
9
|
+
|
|
10
|
+
<script>
|
|
11
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
sticky = false
|
|
15
|
+
brand
|
|
16
|
+
nav
|
|
17
|
+
actions
|
|
18
|
+
class: extra = ''
|
|
19
|
+
...rest
|
|
20
|
+
} := $props()
|
|
21
|
+
|
|
22
|
+
classes := cn 'norns-header', sticky && 'norns-header-sticky', extra
|
|
23
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
section(class!="{classes}")
|
|
2
|
+
+if('image')
|
|
3
|
+
img.hero-image(src!="{image}" alt="" loading="lazy")
|
|
4
|
+
.relative
|
|
5
|
+
h1.hero-title
|
|
6
|
+
+if('gradient')
|
|
7
|
+
GradientText
|
|
8
|
+
| {title}
|
|
9
|
+
+if('!gradient')
|
|
10
|
+
| {title}
|
|
11
|
+
+if('description')
|
|
12
|
+
p.hero-description
|
|
13
|
+
| {description}
|
|
14
|
+
+if('children')
|
|
15
|
+
.mt-4
|
|
16
|
+
| {@render children()}
|
|
17
|
+
+if('actions')
|
|
18
|
+
.hero-actions
|
|
19
|
+
| {@render actions()}
|
|
20
|
+
|
|
21
|
+
<script>
|
|
22
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
23
|
+
import GradientText from './GradientText.n'
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
title = ''
|
|
27
|
+
description
|
|
28
|
+
image
|
|
29
|
+
align = 'left'
|
|
30
|
+
gradient = false
|
|
31
|
+
actions
|
|
32
|
+
children
|
|
33
|
+
class: extra = ''
|
|
34
|
+
} := $props()
|
|
35
|
+
|
|
36
|
+
classes := cn 'hero', align === 'center' && 'hero-center', extra
|
|
37
|
+
</script>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
nav.nav-menu(class!="{classes}" aria-label!="{label}")
|
|
2
|
+
ul.nav-menu-list
|
|
3
|
+
+each('items as item')
|
|
4
|
+
li.nav-menu-item
|
|
5
|
+
+if('item.children')
|
|
6
|
+
button.nav-menu-trigger(type="button" aria-haspopup="true" aria-expanded="false")
|
|
7
|
+
span {item.label}
|
|
8
|
+
Icon(name="lucide:chevron-down" size="size-3.5" class="ml-1 nav-menu-chevron")
|
|
9
|
+
.nav-menu-content
|
|
10
|
+
ul.flex.flex-col.gap-0.5.p-1(class="min-w-[12rem]")
|
|
11
|
+
+each('item.children as child')
|
|
12
|
+
li
|
|
13
|
+
a.nav-menu-link(href!="{child.href ?? '#'}" onclick!="{(e) => onClickItem(e, child)}")
|
|
14
|
+
+if('child.icon')
|
|
15
|
+
Icon(name!="{child.icon}" size="size-4" class="text-fg-muted shrink-0")
|
|
16
|
+
span.flex-1 {child.label}
|
|
17
|
+
+if('!item.children')
|
|
18
|
+
a.nav-menu-trigger(href!="{item.href ?? '#'}" onclick!="{(e) => onClickItem(e, item)}")
|
|
19
|
+
span {item.label}
|
|
20
|
+
|
|
21
|
+
<script>
|
|
22
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
23
|
+
import Icon from './Icon.n'
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
items = []
|
|
27
|
+
label = 'Main navigation'
|
|
28
|
+
onSelect
|
|
29
|
+
class: extra = ''
|
|
30
|
+
} := $props()
|
|
31
|
+
|
|
32
|
+
classes := cn '', extra
|
|
33
|
+
|
|
34
|
+
onClickItem := (e, item) =>
|
|
35
|
+
if onSelect
|
|
36
|
+
e.preventDefault()
|
|
37
|
+
onSelect item
|
|
38
|
+
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
IconifyIcon(icon!="{name}" class!="{classes}" flip!="{flip}" rotate!="{rotate}")
|
|
2
|
+
|
|
3
|
+
<script>
|
|
4
|
+
import IconifyIcon from '@iconify/svelte'
|
|
5
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
name
|
|
9
|
+
size = 'size-4'
|
|
10
|
+
flip
|
|
11
|
+
rotate
|
|
12
|
+
class: extra = ''
|
|
13
|
+
} := $props()
|
|
14
|
+
|
|
15
|
+
classes := cn 'inline-block shrink-0 align-[-0.125em]', size, extra
|
|
16
|
+
</script>
|