@human-synthesis/norns-ui 0.0.5 → 0.0.7
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 +32 -20
- package/package.json +7 -3
- package/src/auto-import.js +1 -4
- package/src/components/Accordion.n +24 -23
- package/src/components/Autocomplete.n +108 -33
- package/src/components/Collapsible.n +8 -11
- package/src/components/ContextMenu.n +55 -36
- package/src/components/DatePicker.n +19 -31
- package/src/components/DateRangePicker.n +17 -29
- package/src/components/Dialog.n +35 -22
- package/src/components/Dropdown.n +54 -23
- package/src/components/MultiSelect.n +138 -36
- package/src/components/Popover.n +30 -15
- package/src/components/ScrollArea.n +9 -20
- package/src/components/Sheet.n +36 -22
- package/src/components/Tabs.n +39 -13
- package/src/components/TimePicker.n +52 -65
- package/src/components/ToggleButton.n +18 -12
- package/src/components/ToggleButtonGroup.n +31 -11
- package/src/components/Tooltip.n +52 -19
- package/src/index.js +1 -4
- 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/styles/atoms.css +628 -920
- package/src/styles/tokens.css +33 -0
- package/src/types/Collapsible.d.ts +1 -0
- package/src/types/ContextMenu.d.ts +6 -1
- package/src/types/DatePicker.d.ts +7 -2
- package/src/types/DateRangePicker.d.ts +3 -3
- package/src/types/Dialog.d.ts +4 -0
- package/src/types/Dropdown.d.ts +2 -1
- package/src/types/MultiSelect.d.ts +3 -0
- package/src/types/ScrollArea.d.ts +1 -1
- package/src/types/Sheet.d.ts +3 -0
- package/src/types/Tabs.d.ts +2 -0
- package/src/types/ToggleButton.d.ts +2 -1
- package/src/types/ToggleButtonGroup.d.ts +2 -1
- package/src/types/Tooltip.d.ts +3 -0
- package/src/components/Drawer.n +0 -49
- package/src/components/PreviewCard.n +0 -30
- package/src/components/RichTooltip.n +0 -33
- package/src/types/Drawer.d.ts +0 -18
- package/src/types/PreviewCard.d.ts +0 -16
- package/src/types/RichTooltip.d.ts +0 -16
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>
|
|
@@ -1,56 +1,158 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
1
|
+
.tags-input.multi-select(
|
|
2
|
+
use:refAction
|
|
3
|
+
bind:this!="{wrapEl}"
|
|
4
|
+
class!="{hasError ? 'tags-input-err' : ''}"
|
|
5
|
+
onclick!="{focusInput}"
|
|
6
|
+
)
|
|
7
|
+
+each('value as v')
|
|
8
|
+
span.chip
|
|
9
|
+
| {labelOf(v)}
|
|
10
|
+
button.chip-remove(
|
|
11
|
+
type="button"
|
|
12
|
+
aria-label="Remove"
|
|
13
|
+
onclick!="{(e) => { e.stopPropagation(); unpick(v) }}"
|
|
14
|
+
onpointerdown!="{(e) => e.stopPropagation()}"
|
|
15
|
+
)
|
|
16
|
+
Icon(name="lucide:x" size="size-3")
|
|
17
|
+
input.tags-input-field(
|
|
18
|
+
bind:this!="{inputEl}"
|
|
19
|
+
type="text"
|
|
20
|
+
bind:value!="{term}"
|
|
21
|
+
placeholder!="{value.length === 0 ? placeholder : ''}"
|
|
22
|
+
disabled!="{disabled}"
|
|
23
|
+
role="combobox"
|
|
24
|
+
aria-expanded!="{open}"
|
|
25
|
+
aria-controls!="{listboxId}"
|
|
26
|
+
aria-autocomplete="list"
|
|
27
|
+
id!="{resolvedId}"
|
|
28
|
+
aria-invalid!="{hasError ? 'true' : undefined}"
|
|
29
|
+
oninput!="{onInput}"
|
|
30
|
+
onkeydown!="{onKeyDown}"
|
|
31
|
+
onfocus!="{() => open = true}"
|
|
32
|
+
onblur!="{onBlur}"
|
|
33
|
+
)
|
|
34
|
+
button.multi-select-toggle.shrink-0(
|
|
35
|
+
type="button"
|
|
36
|
+
tabindex="-1"
|
|
37
|
+
aria-label="Toggle options"
|
|
38
|
+
onclick!="{(e) => { e.stopPropagation(); toggle() }}"
|
|
39
|
+
)
|
|
40
|
+
Icon(name="lucide:chevrons-up-down" size="size-4")
|
|
41
|
+
|
|
42
|
+
+if('open && filtered.length > 0')
|
|
43
|
+
ul(
|
|
44
|
+
use:portal
|
|
45
|
+
use:floatAction
|
|
46
|
+
use:clickOutside!="{[onClose, open, wrapEl]}"
|
|
47
|
+
class="dropdown-content combobox-listbox"
|
|
48
|
+
data-state="open"
|
|
49
|
+
id!="{listboxId}"
|
|
50
|
+
role="listbox"
|
|
51
|
+
aria-multiselectable="true"
|
|
52
|
+
)
|
|
53
|
+
+each('filtered as item, i (item.value)')
|
|
54
|
+
li.dropdown-item(
|
|
55
|
+
role="option"
|
|
56
|
+
aria-selected!="{value.includes(item.value)}"
|
|
57
|
+
data-active!="{i === active ? 'true' : undefined}"
|
|
58
|
+
onpointerdown!="{(e) => { e.preventDefault(); pick(item) }}"
|
|
59
|
+
)
|
|
60
|
+
Icon(name="lucide:check" size="size-3.5" class!="{value.includes(item.value) ? '' : 'opacity-0'}")
|
|
61
|
+
span {item.label}
|
|
62
|
+
+if('open && filtered.length === 0')
|
|
63
|
+
div(use:portal use:floatAction use:clickOutside!="{[onClose, open, wrapEl]}" class="dropdown-content combobox-listbox" data-state="open")
|
|
64
|
+
.combobox-empty No matches
|
|
25
65
|
|
|
26
66
|
<script>
|
|
27
|
-
import {
|
|
67
|
+
import { getContext } from 'svelte'
|
|
28
68
|
import Icon from './Icon.n'
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
Input: ComboboxInput
|
|
33
|
-
Trigger: ComboboxTrigger
|
|
34
|
-
Portal: ComboboxPortal
|
|
35
|
-
Content: ComboboxContent
|
|
36
|
-
Viewport: ComboboxViewport
|
|
37
|
-
Item: ComboboxItem
|
|
38
|
-
} := Combobox
|
|
69
|
+
import { portal } from '@human-synthesis/norns-ui/behaviors/portal.svelte.js'
|
|
70
|
+
import { clickOutside } from '@human-synthesis/norns-ui/behaviors/clickOutside.svelte.js'
|
|
71
|
+
import { useFloating } from '@human-synthesis/norns-ui/behaviors/floating.svelte.js'
|
|
39
72
|
|
|
40
73
|
{
|
|
41
74
|
items = []
|
|
42
75
|
value = $bindable([])
|
|
43
76
|
open = $bindable(false)
|
|
44
|
-
placeholder = '
|
|
77
|
+
placeholder = 'Pick options…'
|
|
45
78
|
disabled = false
|
|
79
|
+
name
|
|
80
|
+
id: providedId
|
|
81
|
+
error: explicitError = false
|
|
46
82
|
} .= $props()
|
|
47
83
|
|
|
84
|
+
field := getContext 'norns-ui:field'
|
|
85
|
+
resolvedId := providedId ?? field?.id
|
|
86
|
+
hasError := explicitError || field?.hasError || false
|
|
87
|
+
|
|
88
|
+
uid := Math.random().toString(36).slice(2, 8)
|
|
89
|
+
listboxId := `listbox-${uid}`
|
|
90
|
+
|
|
91
|
+
{ reference: refAction, floating: floatAction } := useFloating
|
|
92
|
+
placement: 'bottom-start'
|
|
93
|
+
offset: 4
|
|
94
|
+
matchWidth: true
|
|
95
|
+
|
|
48
96
|
term .= $state ''
|
|
97
|
+
active .= $state 0
|
|
98
|
+
inputEl .= $state null
|
|
99
|
+
wrapEl .= $state null
|
|
49
100
|
|
|
50
101
|
filtered := $derived.by =>
|
|
51
102
|
return items unless term
|
|
52
103
|
t := term.toLowerCase()
|
|
53
104
|
items.filter (it) => it.label.toLowerCase().includes(t)
|
|
54
105
|
|
|
106
|
+
$effect =>
|
|
107
|
+
active = 0 if active >= filtered.length
|
|
108
|
+
|
|
55
109
|
labelOf := (v) => items.find((it) => it.value === v)?.label ?? v
|
|
110
|
+
|
|
111
|
+
focusInput := => inputEl?.focus()
|
|
112
|
+
|
|
113
|
+
onInput := =>
|
|
114
|
+
open = true
|
|
115
|
+
active = 0
|
|
116
|
+
|
|
117
|
+
toggle := =>
|
|
118
|
+
open = !open
|
|
119
|
+
inputEl?.focus() if open
|
|
120
|
+
|
|
121
|
+
onClose := => open = false
|
|
122
|
+
|
|
123
|
+
onBlur := =>
|
|
124
|
+
setTimeout (=> open = false), 100
|
|
125
|
+
|
|
126
|
+
pick := (item) =>
|
|
127
|
+
if value.includes(item.value)
|
|
128
|
+
value = value.filter (v) => v !== item.value
|
|
129
|
+
else
|
|
130
|
+
value = [...value, item.value]
|
|
131
|
+
term = ''
|
|
132
|
+
|
|
133
|
+
unpick := (v) =>
|
|
134
|
+
value = value.filter (x) => x !== v
|
|
135
|
+
inputEl?.focus()
|
|
136
|
+
|
|
137
|
+
onKeyDown := (e) =>
|
|
138
|
+
switch e.key
|
|
139
|
+
when 'ArrowDown'
|
|
140
|
+
e.preventDefault()
|
|
141
|
+
open = true
|
|
142
|
+
active = Math.min(filtered.length - 1, active + 1)
|
|
143
|
+
when 'ArrowUp'
|
|
144
|
+
e.preventDefault()
|
|
145
|
+
open = true
|
|
146
|
+
active = Math.max(0, active - 1)
|
|
147
|
+
when 'Enter'
|
|
148
|
+
if open && filtered[active]
|
|
149
|
+
e.preventDefault()
|
|
150
|
+
pick filtered[active]
|
|
151
|
+
when 'Backspace'
|
|
152
|
+
if !term && value.length > 0
|
|
153
|
+
value = value.slice(0, -1)
|
|
154
|
+
when 'Escape'
|
|
155
|
+
open = false
|
|
156
|
+
when 'Tab'
|
|
157
|
+
open = false
|
|
56
158
|
</script>
|
package/src/components/Popover.n
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
+
class!="{contentClasses}"
|
|
12
|
+
data-state="open"
|
|
13
|
+
role="dialog"
|
|
14
|
+
)
|
|
15
|
+
| {@render children?.()}
|
|
8
16
|
|
|
9
17
|
<script>
|
|
10
|
-
import { Popover } from 'bits-ui'
|
|
11
18
|
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
12
|
-
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Portal: PopoverPortal
|
|
17
|
-
Content: PopoverContent
|
|
18
|
-
} := Popover
|
|
19
|
+
import { portal } from '@human-synthesis/norns-ui/behaviors/portal.svelte.js'
|
|
20
|
+
import { clickOutside } from '@human-synthesis/norns-ui/behaviors/clickOutside.svelte.js'
|
|
21
|
+
import { escape } from '@human-synthesis/norns-ui/behaviors/escape.svelte.js'
|
|
22
|
+
import { useFloating } from '@human-synthesis/norns-ui/behaviors/floating.svelte.js'
|
|
19
23
|
|
|
20
24
|
{
|
|
21
25
|
open = $bindable(false)
|
|
@@ -28,5 +32,16 @@ PopoverRoot(bind:open)
|
|
|
28
32
|
class: extra = ''
|
|
29
33
|
} .= $props()
|
|
30
34
|
|
|
35
|
+
placement := align === 'center' ? side : `${side}-${align}`
|
|
36
|
+
|
|
37
|
+
{ reference: refAction, floating: floatAction } := useFloating
|
|
38
|
+
placement: placement
|
|
39
|
+
offset: sideOffset
|
|
40
|
+
|
|
31
41
|
contentClasses := cn 'popover-content', extra
|
|
42
|
+
|
|
43
|
+
triggerEl .= $state null
|
|
44
|
+
|
|
45
|
+
toggle := => open = !open
|
|
46
|
+
onClose := => open = false
|
|
32
47
|
</script>
|
|
@@ -1,31 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
| {@render children?.()}
|
|
4
|
-
ScrollAreaScrollbar.scroll-area-scrollbar(orientation="vertical")
|
|
5
|
-
ScrollAreaThumb.scroll-area-thumb
|
|
6
|
-
+if('horizontal')
|
|
7
|
-
ScrollAreaScrollbar.scroll-area-scrollbar.scroll-area-scrollbar-h(orientation="horizontal")
|
|
8
|
-
ScrollAreaThumb.scroll-area-thumb
|
|
9
|
-
ScrollAreaCorner
|
|
1
|
+
div(class!="{classes}")
|
|
2
|
+
| {@render children?.()}
|
|
10
3
|
|
|
11
4
|
<script>
|
|
12
|
-
import { ScrollArea } from 'bits-ui'
|
|
13
5
|
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
14
6
|
|
|
15
7
|
{
|
|
16
|
-
Root: ScrollAreaRoot
|
|
17
|
-
Viewport: ScrollAreaViewport
|
|
18
|
-
Scrollbar: ScrollAreaScrollbar
|
|
19
|
-
Thumb: ScrollAreaThumb
|
|
20
|
-
Corner: ScrollAreaCorner
|
|
21
|
-
} := ScrollArea
|
|
22
|
-
|
|
23
|
-
{
|
|
24
|
-
type = 'hover'
|
|
25
8
|
horizontal = false
|
|
26
9
|
children
|
|
27
10
|
class: extra = ''
|
|
28
11
|
} := $props()
|
|
29
12
|
|
|
30
|
-
|
|
13
|
+
// Atoms defined in atoms.css — see `.norns-scroll-area*` rules. The
|
|
14
|
+
// utility-class approach (`overflow-y-auto overflow-x-hidden`) cannot
|
|
15
|
+
// be used here because Tailwind's content scanner doesn't see strings
|
|
16
|
+
// inside library-side `cn()` calls.
|
|
17
|
+
classes := cn
|
|
18
|
+
horizontal ? 'norns-scroll-area-horizontal' : 'norns-scroll-area'
|
|
19
|
+
extra
|
|
31
20
|
</script>
|
package/src/components/Sheet.n
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
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
|
+
data-side!="{side}"
|
|
19
|
+
use:focusTrap
|
|
20
|
+
use:escape!="{[onClose, open]}"
|
|
21
|
+
)
|
|
8
22
|
+if('title')
|
|
9
|
-
|
|
23
|
+
h2.dialog-title
|
|
10
24
|
| {title}
|
|
11
25
|
+if('description')
|
|
12
|
-
|
|
26
|
+
p.dialog-description
|
|
13
27
|
| {description}
|
|
14
28
|
+if('children')
|
|
15
29
|
.sheet-body
|
|
@@ -18,23 +32,15 @@ DialogRoot(bind:open)
|
|
|
18
32
|
.dialog-actions
|
|
19
33
|
| {@render actions()}
|
|
20
34
|
+if('!hideClose')
|
|
21
|
-
|
|
35
|
+
button.dialog-close(type="button" aria-label="Close" onclick!="{onClose}")
|
|
22
36
|
| ✕
|
|
23
37
|
|
|
24
38
|
<script>
|
|
25
|
-
import { Dialog } from 'bits-ui'
|
|
26
39
|
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
|
|
40
|
+
import { portal } from '@human-synthesis/norns-ui/behaviors/portal.svelte.js'
|
|
41
|
+
import { scrollLock } from '@human-synthesis/norns-ui/behaviors/scrollLock.svelte.js'
|
|
42
|
+
import { focusTrap } from '@human-synthesis/norns-ui/behaviors/focusTrap.svelte.js'
|
|
43
|
+
import { escape } from '@human-synthesis/norns-ui/behaviors/escape.svelte.js'
|
|
38
44
|
|
|
39
45
|
{
|
|
40
46
|
open = $bindable(false)
|
|
@@ -42,6 +48,8 @@ DialogRoot(bind:open)
|
|
|
42
48
|
title
|
|
43
49
|
description
|
|
44
50
|
hideClose = false
|
|
51
|
+
dismissable = true
|
|
52
|
+
ariaLabel
|
|
45
53
|
trigger
|
|
46
54
|
actions
|
|
47
55
|
children
|
|
@@ -52,4 +60,10 @@ DialogRoot(bind:open)
|
|
|
52
60
|
|
|
53
61
|
overlayClasses := cn 'dialog-overlay', overlayClass
|
|
54
62
|
contentClasses := cn 'sheet-content', `sheet-${side}`, extra
|
|
63
|
+
|
|
64
|
+
onClose := => open = false
|
|
65
|
+
|
|
66
|
+
onOverlayClick := (e) =>
|
|
67
|
+
return unless dismissable
|
|
68
|
+
open = false if e.target === e.currentTarget
|
|
55
69
|
</script>
|
package/src/components/Tabs.n
CHANGED
|
@@ -1,28 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
.tabs-root(class!="{rootClasses}")
|
|
2
|
+
.tabs-list(
|
|
3
|
+
role="tablist"
|
|
4
|
+
aria-label!="{label}"
|
|
5
|
+
use:rovingTabindex!="{{ orientation: 'horizontal' }}"
|
|
6
|
+
)
|
|
3
7
|
+each('items as item (item.value)')
|
|
4
|
-
|
|
8
|
+
button.tabs-trigger(
|
|
9
|
+
type="button"
|
|
10
|
+
role="tab"
|
|
11
|
+
id!="{tabId(item.value)}"
|
|
12
|
+
aria-selected!="{value === item.value}"
|
|
13
|
+
aria-controls!="{panelId(item.value)}"
|
|
14
|
+
data-state!="{value === item.value ? 'active' : 'inactive'}"
|
|
15
|
+
data-roving
|
|
16
|
+
disabled!="{item.disabled}"
|
|
17
|
+
onclick!="{() => select(item.value)}"
|
|
18
|
+
)
|
|
5
19
|
| {item.label}
|
|
6
20
|
+each('items as item (item.value)')
|
|
7
|
-
|
|
8
|
-
|
|
21
|
+
.tabs-content(
|
|
22
|
+
role="tabpanel"
|
|
23
|
+
id!="{panelId(item.value)}"
|
|
24
|
+
aria-labelledby!="{tabId(item.value)}"
|
|
25
|
+
tabindex="0"
|
|
26
|
+
hidden!="{value !== item.value ? 'until-found' : undefined}"
|
|
27
|
+
)
|
|
28
|
+
+if('value === item.value')
|
|
29
|
+
| {@render item.panel?.()}
|
|
9
30
|
|
|
10
31
|
<script>
|
|
11
|
-
import { Tabs } from 'bits-ui'
|
|
12
32
|
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
13
|
-
|
|
14
|
-
{
|
|
15
|
-
Root: TabsRoot
|
|
16
|
-
List: TabsList
|
|
17
|
-
Trigger: TabsTrigger
|
|
18
|
-
Content: TabsContent
|
|
19
|
-
} := Tabs
|
|
33
|
+
import { rovingTabindex } from '@human-synthesis/norns-ui/behaviors/rovingTabindex.svelte.js'
|
|
20
34
|
|
|
21
35
|
{
|
|
22
36
|
value = $bindable()
|
|
23
37
|
items = []
|
|
38
|
+
label = 'Tabs'
|
|
24
39
|
class: extra = ''
|
|
25
40
|
} .= $props()
|
|
26
41
|
|
|
27
42
|
rootClasses := cn 'tabs-root', extra
|
|
43
|
+
|
|
44
|
+
// Lazy default to first tab if no value bound.
|
|
45
|
+
$effect =>
|
|
46
|
+
if value === undefined && items.length > 0 && !items[0].disabled
|
|
47
|
+
value = items[0].value
|
|
48
|
+
|
|
49
|
+
uid := Math.random().toString(36).slice(2, 8)
|
|
50
|
+
tabId := (v) => `tab-${uid}-${v}`
|
|
51
|
+
panelId := (v) => `tabpanel-${uid}-${v}`
|
|
52
|
+
|
|
53
|
+
select := (v) => value = v
|
|
28
54
|
</script>
|