@human-synthesis/norns-ui 0.0.2 → 0.0.5
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 +77 -129
- package/package.json +23 -15
- package/src/auto-import.js +96 -22
- package/src/components/Accordion.n +38 -0
- package/src/components/Audio.n +24 -0
- package/src/components/Autocomplete.n +59 -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 +32 -0
- package/src/components/ColorPicker.n +62 -0
- package/src/components/ContextMenu.n +48 -0
- package/src/components/CopyButton.n +50 -0
- package/src/components/DataTable.n +61 -0
- package/src/components/DatePicker.n +68 -0
- package/src/components/DateRangePicker.n +59 -0
- package/src/components/Dialog.n +54 -0
- package/src/components/Drawer.n +49 -0
- package/src/components/Dropdown.n +41 -0
- package/src/components/Field.n +29 -20
- package/src/components/Form.n +22 -0
- 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 +56 -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 +32 -0
- package/src/components/PreviewCard.n +30 -0
- package/src/components/Progress.n +26 -0
- package/src/components/ProgressCircular.n +52 -0
- package/src/components/RichTooltip.n +33 -0
- package/src/components/RippleButton.n +56 -0
- package/src/components/ScrollArea.n +31 -0
- package/src/components/Separator.n +12 -0
- package/src/components/Sheet.n +55 -0
- 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 +28 -0
- package/src/components/TagsInput.n +66 -0
- package/src/components/ThemeToggler.n +43 -0
- package/src/components/TimePicker.n +166 -0
- package/src/components/Timeline.n +27 -0
- package/src/components/ToastProvider.n +16 -0
- package/src/components/ToggleButton.n +29 -0
- package/src/components/ToggleButtonGroup.n +34 -0
- package/src/components/Toolbar.n +15 -0
- package/src/components/Tooltip.n +34 -0
- 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 +76 -0
- package/src/lib/toast.svelte.js +42 -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 +1794 -71
- package/src/styles/tokens.css +171 -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 +12 -0
- package/src/types/ColorPicker.d.ts +15 -0
- package/src/types/ContextMenu.d.ts +19 -0
- package/src/types/CopyButton.d.ts +16 -0
- package/src/types/DataTable.d.ts +27 -0
- package/src/types/DatePicker.d.ts +18 -0
- package/src/types/DateRangePicker.d.ts +17 -0
- package/src/types/Dialog.d.ts +17 -0
- package/src/types/Drawer.d.ts +18 -0
- package/src/types/Dropdown.d.ts +23 -0
- package/src/types/Field.d.ts +14 -3
- package/src/types/Form.d.ts +17 -0
- 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 +13 -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/Popover.d.ts +18 -0
- package/src/types/PreviewCard.d.ts +16 -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/RichTooltip.d.ts +16 -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 +20 -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 +17 -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/ToastProvider.d.ts +15 -0
- package/src/types/ToggleButton.d.ts +15 -0
- package/src/types/ToggleButtonGroup.d.ts +20 -0
- package/src/types/Toolbar.d.ts +12 -0
- package/src/types/Tooltip.d.ts +15 -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,28 @@
|
|
|
1
|
+
TabsRoot(bind:value class!="{rootClasses}")
|
|
2
|
+
TabsList.tabs-list
|
|
3
|
+
+each('items as item (item.value)')
|
|
4
|
+
TabsTrigger.tabs-trigger(value!="{item.value}" disabled!="{item.disabled}")
|
|
5
|
+
| {item.label}
|
|
6
|
+
+each('items as item (item.value)')
|
|
7
|
+
TabsContent.tabs-content(value!="{item.value}")
|
|
8
|
+
| {@render item.panel?.()}
|
|
9
|
+
|
|
10
|
+
<script>
|
|
11
|
+
import { Tabs } from 'bits-ui'
|
|
12
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
Root: TabsRoot
|
|
16
|
+
List: TabsList
|
|
17
|
+
Trigger: TabsTrigger
|
|
18
|
+
Content: TabsContent
|
|
19
|
+
} := Tabs
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
value = $bindable()
|
|
23
|
+
items = []
|
|
24
|
+
class: extra = ''
|
|
25
|
+
} .= $props()
|
|
26
|
+
|
|
27
|
+
rootClasses := cn 'tabs-root', extra
|
|
28
|
+
</script>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
.tags-input(class!="{wrapClasses}" onclick!="{focusInput}")
|
|
2
|
+
+each('value as tag, i')
|
|
3
|
+
span.chip
|
|
4
|
+
| {tag}
|
|
5
|
+
button.chip-remove(type="button" aria-label!="{`Remove ${tag}`}" onclick!="{(e) => { e.stopPropagation(); remove(i) }}")
|
|
6
|
+
Icon(name="lucide:x" size="size-3")
|
|
7
|
+
input.tags-input-field(
|
|
8
|
+
bind:this!="{inputEl}"
|
|
9
|
+
bind:value!="{draft}"
|
|
10
|
+
type="text"
|
|
11
|
+
placeholder!="{placeholder}"
|
|
12
|
+
disabled!="{disabled}"
|
|
13
|
+
onkeydown!="{onKey}"
|
|
14
|
+
onblur!="{commitOnBlur ? commit : undefined}"
|
|
15
|
+
name!="{name}"
|
|
16
|
+
id!="{resolvedId}"
|
|
17
|
+
aria-invalid!="{hasError ? 'true' : undefined}"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
22
|
+
import { getContext } from 'svelte'
|
|
23
|
+
import Icon from './Icon.n'
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
value = $bindable([])
|
|
27
|
+
max
|
|
28
|
+
placeholder = 'Add a tag…'
|
|
29
|
+
commitOnBlur = true
|
|
30
|
+
separators = [',', 'Enter']
|
|
31
|
+
disabled = false
|
|
32
|
+
name
|
|
33
|
+
id: providedId
|
|
34
|
+
error: explicitError = false
|
|
35
|
+
class: extra = ''
|
|
36
|
+
} .= $props()
|
|
37
|
+
|
|
38
|
+
field := getContext 'norns-ui:field'
|
|
39
|
+
resolvedId := providedId ?? field?.id
|
|
40
|
+
hasError := explicitError || field?.hasError || false
|
|
41
|
+
|
|
42
|
+
draft .= $state ''
|
|
43
|
+
inputEl .= $state null
|
|
44
|
+
|
|
45
|
+
wrapClasses := cn 'tags-input-wrap', hasError && 'tags-input-err', extra
|
|
46
|
+
|
|
47
|
+
focusInput := => inputEl?.focus()
|
|
48
|
+
|
|
49
|
+
commit := =>
|
|
50
|
+
t := draft.trim()
|
|
51
|
+
return unless t
|
|
52
|
+
return if max && value.length >= max
|
|
53
|
+
return if value.includes(t)
|
|
54
|
+
value = [...value, t]
|
|
55
|
+
draft = ''
|
|
56
|
+
|
|
57
|
+
remove := (i) =>
|
|
58
|
+
value = value.filter (_, idx) => idx !== i
|
|
59
|
+
|
|
60
|
+
onKey := (e) =>
|
|
61
|
+
if separators.includes(e.key)
|
|
62
|
+
e.preventDefault()
|
|
63
|
+
commit()
|
|
64
|
+
else if e.key === 'Backspace' && !draft && value.length > 0
|
|
65
|
+
remove(value.length - 1)
|
|
66
|
+
</script>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
button(
|
|
2
|
+
type="button"
|
|
3
|
+
class!="{classes}"
|
|
4
|
+
onclick!="{toggle}"
|
|
5
|
+
aria-label="Toggle theme"
|
|
6
|
+
title="Toggle theme"
|
|
7
|
+
)
|
|
8
|
+
Icon(name!="{lightIcon}" size!="{iconSize}" class="theme-toggle-light")
|
|
9
|
+
Icon(name!="{darkIcon}" size!="{iconSize}" class="theme-toggle-dark")
|
|
10
|
+
+if('showLabel')
|
|
11
|
+
span.theme-toggle-light
|
|
12
|
+
| {labelLight}
|
|
13
|
+
span.theme-toggle-dark
|
|
14
|
+
| {labelDark}
|
|
15
|
+
|
|
16
|
+
<script>
|
|
17
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
18
|
+
import Icon from './Icon.n'
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
variant = 'ghost'
|
|
22
|
+
size = 'md'
|
|
23
|
+
showLabel = false
|
|
24
|
+
labelLight = 'Dark'
|
|
25
|
+
labelDark = 'Light'
|
|
26
|
+
lightIcon = 'lucide:moon'
|
|
27
|
+
darkIcon = 'lucide:sun'
|
|
28
|
+
storageKey = 'norns-theme'
|
|
29
|
+
class: extra = ''
|
|
30
|
+
} := $props()
|
|
31
|
+
|
|
32
|
+
iconSize := size === 'sm' ? 'size-3.5' : size === 'lg' ? 'size-5' : 'size-4'
|
|
33
|
+
|
|
34
|
+
classes := cn 'btn' , `btn-${variant}`, size !== 'md' && `btn-${size}`, extra
|
|
35
|
+
|
|
36
|
+
toggle := =>
|
|
37
|
+
root := document.documentElement
|
|
38
|
+
next := root.dataset.theme === 'dark' ? 'light' : 'dark'
|
|
39
|
+
root.dataset.theme = next === 'dark' ? 'dark' : ''
|
|
40
|
+
try
|
|
41
|
+
localStorage.setItem storageKey, next
|
|
42
|
+
catch _
|
|
43
|
+
</script>
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
PopoverRoot(bind:open)
|
|
2
|
+
PopoverTrigger.input.date-picker-trigger(
|
|
3
|
+
type="button"
|
|
4
|
+
disabled!="{disabled}"
|
|
5
|
+
id!="{resolvedId}"
|
|
6
|
+
aria-label!="{label ?? 'Pick a time'}"
|
|
7
|
+
class!="{hasError ? 'input-err' : ''}"
|
|
8
|
+
)
|
|
9
|
+
Icon(name="lucide:clock" size="size-4" class="text-fg-muted shrink-0")
|
|
10
|
+
span.flex-1.text-left.font-mono.tabular-nums
|
|
11
|
+
| {value || placeholder}
|
|
12
|
+
Icon(name="lucide:chevron-down" size="size-3.5" class="text-fg-muted shrink-0")
|
|
13
|
+
PopoverPortal
|
|
14
|
+
PopoverContent.popover-content(
|
|
15
|
+
class="p-2 time-picker-popover"
|
|
16
|
+
onOpenAutoFocus!="{(e) => e.preventDefault()}"
|
|
17
|
+
onCloseAutoFocus!="{(e) => e.preventDefault()}"
|
|
18
|
+
)
|
|
19
|
+
.time-picker-grid
|
|
20
|
+
.time-picker-col(role="listbox" aria-label="Hour" bind:this!="{hourCol}")
|
|
21
|
+
+each('hours as h')
|
|
22
|
+
button.time-picker-cell(
|
|
23
|
+
type="button"
|
|
24
|
+
class!="{h === currentHour ? 'time-picker-cell-selected' : ''}"
|
|
25
|
+
onclick!="{() => setHour(h)}"
|
|
26
|
+
data-time-h!="{h}"
|
|
27
|
+
)
|
|
28
|
+
| {pad(h)}
|
|
29
|
+
.time-picker-sep :
|
|
30
|
+
.time-picker-col(role="listbox" aria-label="Minute" bind:this!="{minuteCol}")
|
|
31
|
+
+each('minutes as m')
|
|
32
|
+
button.time-picker-cell(
|
|
33
|
+
type="button"
|
|
34
|
+
class!="{m === currentMinute ? 'time-picker-cell-selected' : ''}"
|
|
35
|
+
onclick!="{() => setMinute(m)}"
|
|
36
|
+
data-time-m!="{m}"
|
|
37
|
+
)
|
|
38
|
+
| {pad(m)}
|
|
39
|
+
+if('hourCycle === 12')
|
|
40
|
+
.time-picker-col(role="listbox" aria-label="AM/PM")
|
|
41
|
+
button.time-picker-cell(
|
|
42
|
+
type="button"
|
|
43
|
+
class!="{period === 'AM' ? 'time-picker-cell-selected' : ''}"
|
|
44
|
+
onclick!="{() => setPeriod('AM')}"
|
|
45
|
+
)
|
|
46
|
+
| AM
|
|
47
|
+
button.time-picker-cell(
|
|
48
|
+
type="button"
|
|
49
|
+
class!="{period === 'PM' ? 'time-picker-cell-selected' : ''}"
|
|
50
|
+
onclick!="{() => setPeriod('PM')}"
|
|
51
|
+
)
|
|
52
|
+
| PM
|
|
53
|
+
.flex.justify-between.gap-2.mt-2.pt-2(class="border-t border-border")
|
|
54
|
+
button.btn.btn-ghost.btn-sm(type="button" onclick!="{clearValue}") Clear
|
|
55
|
+
button.btn.btn-primary.btn-sm(type="button" onclick!="{() => open = false}") Done
|
|
56
|
+
|
|
57
|
+
<script>
|
|
58
|
+
import { Popover } from 'bits-ui'
|
|
59
|
+
import { getContext } from 'svelte'
|
|
60
|
+
import Icon from './Icon.n'
|
|
61
|
+
|
|
62
|
+
{
|
|
63
|
+
Root: PopoverRoot
|
|
64
|
+
Trigger: PopoverTrigger
|
|
65
|
+
Portal: PopoverPortal
|
|
66
|
+
Content: PopoverContent
|
|
67
|
+
} := Popover
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
value = $bindable('')
|
|
71
|
+
open = $bindable(false)
|
|
72
|
+
minuteStep = 5
|
|
73
|
+
hourCycle = 24
|
|
74
|
+
placeholder = 'Pick a time'
|
|
75
|
+
disabled = false
|
|
76
|
+
label
|
|
77
|
+
name
|
|
78
|
+
id: providedId
|
|
79
|
+
error: explicitError = false
|
|
80
|
+
} .= $props()
|
|
81
|
+
|
|
82
|
+
field := getContext 'norns-ui:field'
|
|
83
|
+
resolvedId := providedId ?? field?.id
|
|
84
|
+
hasError := explicitError || field?.hasError || false
|
|
85
|
+
|
|
86
|
+
pad := (n) => String(n).padStart 2, '0'
|
|
87
|
+
|
|
88
|
+
parts := $derived.by =>
|
|
89
|
+
if !value
|
|
90
|
+
return { h: 0, m: 0 }
|
|
91
|
+
[hStr, mStr] := value.split ':'
|
|
92
|
+
{ h: parseInt(hStr ?? '0', 10), m: parseInt(mStr ?? '0', 10) }
|
|
93
|
+
|
|
94
|
+
hours := $derived.by =>
|
|
95
|
+
out := []
|
|
96
|
+
if hourCycle === 12
|
|
97
|
+
out.push 12
|
|
98
|
+
i .= 1
|
|
99
|
+
while i <= 11
|
|
100
|
+
out.push i
|
|
101
|
+
i++
|
|
102
|
+
else
|
|
103
|
+
i .= 0
|
|
104
|
+
while i < 24
|
|
105
|
+
out.push i
|
|
106
|
+
i++
|
|
107
|
+
out
|
|
108
|
+
|
|
109
|
+
minutes := $derived.by =>
|
|
110
|
+
out := []
|
|
111
|
+
i .= 0
|
|
112
|
+
while i < 60
|
|
113
|
+
out.push i
|
|
114
|
+
i += minuteStep
|
|
115
|
+
out
|
|
116
|
+
|
|
117
|
+
currentHour := $derived.by =>
|
|
118
|
+
h := parts.h
|
|
119
|
+
if hourCycle === 12
|
|
120
|
+
h === 0 ? 12 : (h > 12 ? h - 12 : h)
|
|
121
|
+
else
|
|
122
|
+
h
|
|
123
|
+
|
|
124
|
+
currentMinute := $derived parts.m
|
|
125
|
+
period := $derived parts.h >= 12 ? 'PM' : 'AM'
|
|
126
|
+
|
|
127
|
+
emit := (h24, m) =>
|
|
128
|
+
value = `${pad(h24)}:${pad(m)}`
|
|
129
|
+
|
|
130
|
+
setHour := (displayed) =>
|
|
131
|
+
nextH .= displayed
|
|
132
|
+
if hourCycle === 12
|
|
133
|
+
if period === 'PM'
|
|
134
|
+
nextH = displayed === 12 ? 12 : displayed + 12
|
|
135
|
+
else
|
|
136
|
+
nextH = displayed === 12 ? 0 : displayed
|
|
137
|
+
emit nextH, parts.m
|
|
138
|
+
|
|
139
|
+
setMinute := (m) => emit parts.h, m
|
|
140
|
+
|
|
141
|
+
setPeriod := (p) =>
|
|
142
|
+
nextH .= parts.h
|
|
143
|
+
if p === 'PM' && parts.h < 12
|
|
144
|
+
nextH = parts.h + 12
|
|
145
|
+
else if p === 'AM' && parts.h >= 12
|
|
146
|
+
nextH = parts.h - 12
|
|
147
|
+
emit nextH, parts.m
|
|
148
|
+
|
|
149
|
+
clearValue := => value = ''
|
|
150
|
+
|
|
151
|
+
hourCol .= $state null
|
|
152
|
+
minuteCol .= $state null
|
|
153
|
+
|
|
154
|
+
// When the popover opens, scroll each column to its currently-selected cell.
|
|
155
|
+
$effect =>
|
|
156
|
+
return unless open && (hourCol || minuteCol)
|
|
157
|
+
queueMicrotask =>
|
|
158
|
+
scrollSelected hourCol, currentHour, 'data-time-h'
|
|
159
|
+
scrollSelected minuteCol, currentMinute, 'data-time-m'
|
|
160
|
+
|
|
161
|
+
scrollSelected := (col, value, attr) =>
|
|
162
|
+
return unless col
|
|
163
|
+
target := col.querySelector `[${attr}="${value}"]`
|
|
164
|
+
return unless target
|
|
165
|
+
col.scrollTop = target.offsetTop - col.clientHeight / 2 + target.clientHeight / 2
|
|
166
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
ol(class!="{classes}")
|
|
2
|
+
+each('items as item, i')
|
|
3
|
+
li.timeline-item
|
|
4
|
+
.timeline-dot(class!="{item.variant ? `timeline-dot-${item.variant}` : ''}")
|
|
5
|
+
+if('item.icon')
|
|
6
|
+
Icon(name!="{item.icon}" size="size-3")
|
|
7
|
+
+if('i < items.length - 1')
|
|
8
|
+
.timeline-line
|
|
9
|
+
.timeline-body
|
|
10
|
+
+if('item.time')
|
|
11
|
+
.timeline-time {item.time}
|
|
12
|
+
+if('item.title')
|
|
13
|
+
.timeline-title {item.title}
|
|
14
|
+
+if('item.description')
|
|
15
|
+
.timeline-description {item.description}
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
19
|
+
import Icon from './Icon.n'
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
items = []
|
|
23
|
+
class: extra = ''
|
|
24
|
+
} := $props()
|
|
25
|
+
|
|
26
|
+
classes := cn 'timeline', extra
|
|
27
|
+
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
div(role="region" aria-label="Notifications" class!="{regionClasses}")
|
|
2
|
+
+each('toastStore.items as item (item.id)')
|
|
3
|
+
div(class!="{`toast toast-${item.variant}`}" role="status" data-id!="{item.id}")
|
|
4
|
+
.toast-message
|
|
5
|
+
| {item.message}
|
|
6
|
+
button.toast-close(type="button" aria-label="Dismiss" onclick!="{() => dismiss(item.id)}")
|
|
7
|
+
| ✕
|
|
8
|
+
|
|
9
|
+
<script>
|
|
10
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
11
|
+
import { toastStore, dismiss } from '@human-synthesis/norns-ui/toast'
|
|
12
|
+
|
|
13
|
+
{ class: extra = '' } := $props()
|
|
14
|
+
|
|
15
|
+
regionClasses := cn 'toast-region', extra
|
|
16
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
ToggleRoot(bind:pressed class!="{classes}" disabled!="{disabled}" onPressedChange!="{onpressedchange}")
|
|
2
|
+
+if('icon')
|
|
3
|
+
Icon(name!="{icon}" size="size-4")
|
|
4
|
+
| {@render children?.()}
|
|
5
|
+
|
|
6
|
+
<script>
|
|
7
|
+
import { Toggle } from 'bits-ui'
|
|
8
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
9
|
+
import Icon from './Icon.n'
|
|
10
|
+
|
|
11
|
+
{ Root: ToggleRoot } := Toggle
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
pressed = $bindable(false)
|
|
15
|
+
disabled = false
|
|
16
|
+
icon
|
|
17
|
+
variant = 'default'
|
|
18
|
+
size = 'md'
|
|
19
|
+
onpressedchange
|
|
20
|
+
children
|
|
21
|
+
class: extra = ''
|
|
22
|
+
} .= $props()
|
|
23
|
+
|
|
24
|
+
classes := cn
|
|
25
|
+
'toggle-btn'
|
|
26
|
+
variant !== 'default' && `toggle-btn-${variant}`
|
|
27
|
+
size !== 'md' && `toggle-btn-${size}`
|
|
28
|
+
extra
|
|
29
|
+
</script>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
ToggleGroupRoot(
|
|
2
|
+
type!="{multiple ? 'multiple' : 'single'}"
|
|
3
|
+
bind:value
|
|
4
|
+
class!="{classes}"
|
|
5
|
+
disabled!="{disabled}"
|
|
6
|
+
)
|
|
7
|
+
+each('items as item')
|
|
8
|
+
ToggleGroupItem.toggle-btn(
|
|
9
|
+
value!="{item.value}"
|
|
10
|
+
disabled!="{item.disabled}"
|
|
11
|
+
aria-label!="{item.label}"
|
|
12
|
+
)
|
|
13
|
+
+if('item.icon')
|
|
14
|
+
Icon(name!="{item.icon}" size="size-4")
|
|
15
|
+
+if('item.label && !item.iconOnly')
|
|
16
|
+
span {item.label}
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { ToggleGroup } from 'bits-ui'
|
|
20
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
21
|
+
import Icon from './Icon.n'
|
|
22
|
+
|
|
23
|
+
{ Root: ToggleGroupRoot, Item: ToggleGroupItem } := ToggleGroup
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
items = []
|
|
27
|
+
value = $bindable(undefined)
|
|
28
|
+
multiple = false
|
|
29
|
+
disabled = false
|
|
30
|
+
class: extra = ''
|
|
31
|
+
} .= $props()
|
|
32
|
+
|
|
33
|
+
classes := cn 'btn-group toggle-group', extra
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
div(role="toolbar" aria-label!="{label}" aria-orientation!="{orientation}" class!="{classes}")
|
|
2
|
+
| {@render children?.()}
|
|
3
|
+
|
|
4
|
+
<script>
|
|
5
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
orientation = 'horizontal'
|
|
9
|
+
label
|
|
10
|
+
children
|
|
11
|
+
class: extra = ''
|
|
12
|
+
} := $props()
|
|
13
|
+
|
|
14
|
+
classes := cn 'toolbar', `toolbar-${orientation}`, extra
|
|
15
|
+
</script>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
TooltipProvider(delayDuration!="{delay}")
|
|
2
|
+
TooltipRoot
|
|
3
|
+
TooltipTrigger(class!="{triggerClass ?? undefined}")
|
|
4
|
+
| {@render trigger?.()}
|
|
5
|
+
TooltipPortal
|
|
6
|
+
TooltipContent(class!="{contentClasses}" side!="{side}" sideOffset!="{sideOffset}")
|
|
7
|
+
| {content ?? ''}
|
|
8
|
+
| {@render children?.()}
|
|
9
|
+
|
|
10
|
+
<script>
|
|
11
|
+
import { Tooltip } from 'bits-ui'
|
|
12
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
13
|
+
|
|
14
|
+
{
|
|
15
|
+
Provider: TooltipProvider
|
|
16
|
+
Root: TooltipRoot
|
|
17
|
+
Trigger: TooltipTrigger
|
|
18
|
+
Portal: TooltipPortal
|
|
19
|
+
Content: TooltipContent
|
|
20
|
+
} := Tooltip
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
content
|
|
24
|
+
side = 'top'
|
|
25
|
+
sideOffset = 6
|
|
26
|
+
delay = 300
|
|
27
|
+
trigger
|
|
28
|
+
children
|
|
29
|
+
triggerClass
|
|
30
|
+
class: extra = ''
|
|
31
|
+
} .= $props()
|
|
32
|
+
|
|
33
|
+
contentClasses := cn 'tooltip-content', extra
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
ul.tree(class!="{classes}" role="tree")
|
|
2
|
+
+each('items as node')
|
|
3
|
+
| {@render renderNode(node, 0)}
|
|
4
|
+
|
|
5
|
+
+snippet('renderNode', node, depth)
|
|
6
|
+
li.tree-node(
|
|
7
|
+
role="treeitem"
|
|
8
|
+
aria-expanded!="{hasChildren(node) ? isExpanded(node.id) : undefined}"
|
|
9
|
+
aria-selected!="{node.id === selected}"
|
|
10
|
+
)
|
|
11
|
+
button.tree-row(
|
|
12
|
+
type="button"
|
|
13
|
+
class!="{node.id === selected ? 'tree-row-selected' : ''}"
|
|
14
|
+
style!="{`padding-left: ${0.5 + depth * 1.25}rem`}"
|
|
15
|
+
onclick!="{() => onRowClick(node)}"
|
|
16
|
+
)
|
|
17
|
+
+if('hasChildren(node)')
|
|
18
|
+
span.tree-chevron(class!="{isExpanded(node.id) ? 'tree-chevron-open' : ''}")
|
|
19
|
+
Icon(name="lucide:chevron-right" size="size-3.5")
|
|
20
|
+
+if('!hasChildren(node)')
|
|
21
|
+
span.tree-chevron-spacer
|
|
22
|
+
+if('node.icon')
|
|
23
|
+
Icon(name!="{node.icon}" size="size-4" class="text-fg-muted shrink-0")
|
|
24
|
+
span.tree-label
|
|
25
|
+
| {node.label}
|
|
26
|
+
+if('hasChildren(node) && isExpanded(node.id)')
|
|
27
|
+
ul(role="group")
|
|
28
|
+
+each('node.children as child')
|
|
29
|
+
| {@render renderNode(child, depth + 1)}
|
|
30
|
+
|
|
31
|
+
<script>
|
|
32
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
33
|
+
import Icon from './Icon.n'
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
items = []
|
|
37
|
+
expanded = $bindable([])
|
|
38
|
+
selected = $bindable(undefined)
|
|
39
|
+
class: extra = ''
|
|
40
|
+
} .= $props()
|
|
41
|
+
|
|
42
|
+
classes := cn 'tree', extra
|
|
43
|
+
|
|
44
|
+
hasChildren := (node) => Array.isArray(node.children) && node.children.length > 0
|
|
45
|
+
isExpanded := (id) => expanded.includes(id)
|
|
46
|
+
|
|
47
|
+
toggle := (id) =>
|
|
48
|
+
if isExpanded(id)
|
|
49
|
+
expanded = expanded.filter (x) => x !== id
|
|
50
|
+
else
|
|
51
|
+
expanded = [...expanded, id]
|
|
52
|
+
|
|
53
|
+
onRowClick := (node) =>
|
|
54
|
+
if hasChildren(node)
|
|
55
|
+
toggle node.id
|
|
56
|
+
selected = node.id
|
|
57
|
+
</script>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
.uploader(
|
|
2
|
+
class!="{classes}"
|
|
3
|
+
ondragover!="{onDragOver}"
|
|
4
|
+
ondragleave!="{onDragLeave}"
|
|
5
|
+
ondrop!="{onDrop}"
|
|
6
|
+
role="button"
|
|
7
|
+
tabindex="0"
|
|
8
|
+
onclick!="{() => inputEl?.click()}"
|
|
9
|
+
onkeydown!="{(e) => { if (e.key === 'Enter' || e.key === ' ') inputEl?.click() }}"
|
|
10
|
+
)
|
|
11
|
+
input(
|
|
12
|
+
type="file"
|
|
13
|
+
bind:this!="{inputEl}"
|
|
14
|
+
multiple!="{multiple}"
|
|
15
|
+
accept!="{accept}"
|
|
16
|
+
disabled!="{disabled}"
|
|
17
|
+
class="sr-only"
|
|
18
|
+
onchange!="{(e) => onPick(e.currentTarget.files)}"
|
|
19
|
+
)
|
|
20
|
+
Icon(name="lucide:upload-cloud" size="size-8" class="text-fg-muted")
|
|
21
|
+
.uploader-text
|
|
22
|
+
span.uploader-primary
|
|
23
|
+
| {placeholder}
|
|
24
|
+
+if('helper')
|
|
25
|
+
span.uploader-helper
|
|
26
|
+
| {helper}
|
|
27
|
+
|
|
28
|
+
+if('files.length > 0')
|
|
29
|
+
ul.uploader-list
|
|
30
|
+
+each('files as file, i')
|
|
31
|
+
li.uploader-item
|
|
32
|
+
Icon(name="lucide:file" size="size-4" class="text-fg-muted shrink-0")
|
|
33
|
+
.flex-1.min-w-0
|
|
34
|
+
.text-sm.truncate {file.name}
|
|
35
|
+
.text-xs(class="text-fg-muted") {humanSize(file.size)}
|
|
36
|
+
button.btn.btn-ghost.btn-sm(type="button" aria-label="Remove" onclick!="{() => remove(i)}")
|
|
37
|
+
Icon(name="lucide:x" size="size-3.5")
|
|
38
|
+
|
|
39
|
+
<script>
|
|
40
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
41
|
+
import Icon from './Icon.n'
|
|
42
|
+
|
|
43
|
+
{
|
|
44
|
+
files = $bindable([])
|
|
45
|
+
multiple = true
|
|
46
|
+
accept
|
|
47
|
+
disabled = false
|
|
48
|
+
placeholder = 'Drop files here or click to browse'
|
|
49
|
+
helper
|
|
50
|
+
onchange
|
|
51
|
+
class: extra = ''
|
|
52
|
+
} .= $props()
|
|
53
|
+
|
|
54
|
+
inputEl .= $state null
|
|
55
|
+
dragging .= $state false
|
|
56
|
+
|
|
57
|
+
classes := cn 'uploader-root', dragging && 'uploader-dragging', extra
|
|
58
|
+
|
|
59
|
+
humanSize := (n) =>
|
|
60
|
+
return '' unless n
|
|
61
|
+
if n < 1024
|
|
62
|
+
`${n} B`
|
|
63
|
+
else if n < 1024 * 1024
|
|
64
|
+
`${(n / 1024).toFixed(1)} KB`
|
|
65
|
+
else
|
|
66
|
+
`${(n / 1024 / 1024).toFixed(1)} MB`
|
|
67
|
+
|
|
68
|
+
onPick := (list) =>
|
|
69
|
+
return unless list
|
|
70
|
+
next := Array.from list
|
|
71
|
+
files = multiple ? [...files, ...next] : next.slice(0, 1)
|
|
72
|
+
onchange?.(files)
|
|
73
|
+
|
|
74
|
+
remove := (i) =>
|
|
75
|
+
files = files.filter (_, idx) => idx !== i
|
|
76
|
+
onchange?.(files)
|
|
77
|
+
|
|
78
|
+
onDragOver := (e) =>
|
|
79
|
+
e.preventDefault()
|
|
80
|
+
dragging = true
|
|
81
|
+
|
|
82
|
+
onDragLeave := => dragging = false
|
|
83
|
+
|
|
84
|
+
onDrop := (e) =>
|
|
85
|
+
e.preventDefault()
|
|
86
|
+
dragging = false
|
|
87
|
+
onPick e.dataTransfer?.files
|
|
88
|
+
</script>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
video(src!="{src}" controls!="{controls}" autoplay!="{autoplay}" loop!="{looping}" muted!="{muted}" playsinline!="{playsinline}" preload!="{preload}" poster!="{poster}" class!="{classes}")
|
|
2
|
+
+each('sources as source')
|
|
3
|
+
source(src!="{source.src}" type!="{source.type}")
|
|
4
|
+
| {fallback}
|
|
5
|
+
|
|
6
|
+
<script>
|
|
7
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
8
|
+
|
|
9
|
+
// Note: HTML attribute `loop` becomes `looping` here because `loop` is a
|
|
10
|
+
// Civet reserved word.
|
|
11
|
+
{
|
|
12
|
+
src
|
|
13
|
+
sources = []
|
|
14
|
+
controls = true
|
|
15
|
+
autoplay = false
|
|
16
|
+
loop: looping = false
|
|
17
|
+
muted = false
|
|
18
|
+
playsinline = true
|
|
19
|
+
preload = 'metadata'
|
|
20
|
+
poster
|
|
21
|
+
fallback = 'Your browser does not support the video element.'
|
|
22
|
+
class: extra = ''
|
|
23
|
+
} := $props()
|
|
24
|
+
|
|
25
|
+
classes := cn 'video-player block w-full rounded-md', extra
|
|
26
|
+
</script>
|