@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,25 @@
|
|
|
1
|
+
.avatar-group(class!="{classes}" role="group" aria-label!="{label}")
|
|
2
|
+
+each('visible as item, i')
|
|
3
|
+
span.avatar-group-item(style!="{`--avatar-group-i: ${i}`}")
|
|
4
|
+
Avatar(src!="{item.src}" name!="{item.name}" size!="{size}")
|
|
5
|
+
+if('overflow > 0')
|
|
6
|
+
span.avatar-group-overflow(class!="{`avatar avatar-${size}`}" aria-hidden="true")
|
|
7
|
+
| +{overflow}
|
|
8
|
+
|
|
9
|
+
<script>
|
|
10
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
11
|
+
import Avatar from './Avatar.n'
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
items = []
|
|
15
|
+
max = 5
|
|
16
|
+
size = 'md'
|
|
17
|
+
label = 'People'
|
|
18
|
+
class: extra = ''
|
|
19
|
+
} := $props()
|
|
20
|
+
|
|
21
|
+
visible := $derived items.slice(0, max)
|
|
22
|
+
overflow := $derived Math.max(0, items.length - max)
|
|
23
|
+
|
|
24
|
+
classes := cn 'avatar-group', extra
|
|
25
|
+
</script>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
span(class!="{classes}")
|
|
2
|
+
| {@render children?.()}
|
|
3
|
+
|
|
4
|
+
<script>
|
|
5
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
variant = 'default'
|
|
9
|
+
size = 'md'
|
|
10
|
+
children
|
|
11
|
+
class: extra = ''
|
|
12
|
+
...rest
|
|
13
|
+
} := $props()
|
|
14
|
+
|
|
15
|
+
classes := cn
|
|
16
|
+
'badge'
|
|
17
|
+
variant !== 'default' && `badge-${variant}`
|
|
18
|
+
size !== 'md' && `badge-${size}`
|
|
19
|
+
extra
|
|
20
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
div(class!="{classes}" role="status")
|
|
2
|
+
+if('icon')
|
|
3
|
+
.banner-icon
|
|
4
|
+
Icon(name!="{icon}" size="size-5")
|
|
5
|
+
.banner-body
|
|
6
|
+
| {@render children?.()}
|
|
7
|
+
+if('actions')
|
|
8
|
+
.banner-actions
|
|
9
|
+
| {@render actions()}
|
|
10
|
+
|
|
11
|
+
<script>
|
|
12
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
13
|
+
import Icon from './Icon.n'
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
variant = 'info'
|
|
17
|
+
icon
|
|
18
|
+
actions
|
|
19
|
+
children
|
|
20
|
+
class: extra = ''
|
|
21
|
+
...rest
|
|
22
|
+
} := $props()
|
|
23
|
+
|
|
24
|
+
classes := cn 'banner', `banner-${variant}`, extra
|
|
25
|
+
</script>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
nav.breadcrumbs(aria-label="Breadcrumb" class!="{extra}")
|
|
2
|
+
ol.flex.items-center.gap-1
|
|
3
|
+
+each('items as item, i')
|
|
4
|
+
li.breadcrumb-item
|
|
5
|
+
+if('item.href && i < items.length - 1')
|
|
6
|
+
a.breadcrumb-link(href!="{item.href}")
|
|
7
|
+
| {item.label}
|
|
8
|
+
+if('!item.href || i === items.length - 1')
|
|
9
|
+
span(class!="{i === items.length - 1 ? 'breadcrumb-current' : ''}")
|
|
10
|
+
| {item.label}
|
|
11
|
+
+if('i < items.length - 1')
|
|
12
|
+
span.breadcrumb-separator(aria-hidden="true")
|
|
13
|
+
| {separator}
|
|
14
|
+
|
|
15
|
+
<script>
|
|
16
|
+
{
|
|
17
|
+
items = []
|
|
18
|
+
separator = '/'
|
|
19
|
+
class: extra = ''
|
|
20
|
+
...rest
|
|
21
|
+
} := $props()
|
|
22
|
+
</script>
|
package/src/components/Btn.n
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
type!="{type}"
|
|
3
|
-
class!="{classes}"
|
|
4
|
-
disabled!="{disabled || loading}"
|
|
5
|
-
onclick!="{onclick}"
|
|
6
|
-
aria-busy!="{loading ? 'true' : undefined}"
|
|
7
|
-
)
|
|
1
|
+
+snippet('body')
|
|
8
2
|
+if('loading')
|
|
9
3
|
span.ui-spinner(aria-hidden="true")
|
|
4
|
+
+if('!loading && icon')
|
|
5
|
+
Icon(name!="{icon}" size!="{iconSize}")
|
|
10
6
|
+if('!loading && leading')
|
|
11
7
|
| {@render leading()}
|
|
12
8
|
+if('children')
|
|
@@ -14,8 +10,28 @@ button(
|
|
|
14
10
|
+if('!loading && trailing')
|
|
15
11
|
| {@render trailing()}
|
|
16
12
|
|
|
13
|
+
+if('href')
|
|
14
|
+
a(
|
|
15
|
+
href!="{href}"
|
|
16
|
+
target!="{target}"
|
|
17
|
+
rel!="{rel}"
|
|
18
|
+
class!="{classes}"
|
|
19
|
+
aria-disabled!="{disabled || loading ? 'true' : undefined}"
|
|
20
|
+
)
|
|
21
|
+
| {@render body()}
|
|
22
|
+
+if('!href')
|
|
23
|
+
button(
|
|
24
|
+
type!="{type}"
|
|
25
|
+
class!="{classes}"
|
|
26
|
+
disabled!="{disabled || loading}"
|
|
27
|
+
onclick!="{onclick}"
|
|
28
|
+
aria-busy!="{loading ? 'true' : undefined}"
|
|
29
|
+
)
|
|
30
|
+
| {@render body()}
|
|
31
|
+
|
|
17
32
|
<script>
|
|
18
33
|
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
34
|
+
import Icon from './Icon.n'
|
|
19
35
|
|
|
20
36
|
{
|
|
21
37
|
variant = 'primary'
|
|
@@ -24,11 +40,16 @@ button(
|
|
|
24
40
|
disabled = false
|
|
25
41
|
type = 'button'
|
|
26
42
|
onclick = undefined
|
|
43
|
+
icon
|
|
44
|
+
href
|
|
45
|
+
target
|
|
46
|
+
rel
|
|
27
47
|
children
|
|
28
48
|
leading
|
|
29
49
|
trailing
|
|
30
50
|
class: extra = ''
|
|
31
51
|
} := $props()
|
|
32
52
|
|
|
33
|
-
classes := cn 'btn', `btn-${variant}`, size !== 'md' && `btn-${size}`, extra
|
|
53
|
+
classes := cn 'btn', `btn-${variant}`, size !== 'md' && `btn-${size}`, 'no-underline', extra
|
|
54
|
+
iconSize := size === 'sm' ? 'size-3.5' : size === 'lg' ? 'size-5' : 'size-4'
|
|
34
55
|
</script>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
div(role="group" class!="{classes}")
|
|
2
|
+
| {@render children?.()}
|
|
3
|
+
|
|
4
|
+
<script>
|
|
5
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
6
|
+
|
|
7
|
+
{
|
|
8
|
+
orientation = 'horizontal'
|
|
9
|
+
children
|
|
10
|
+
class: extra = ''
|
|
11
|
+
} := $props()
|
|
12
|
+
|
|
13
|
+
classes := cn 'btn-group', orientation === 'vertical' && 'btn-group-vertical', extra
|
|
14
|
+
</script>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
.calendar(class!="{classes}" role="application" aria-label!="{label}")
|
|
2
|
+
.calendar-header
|
|
3
|
+
button.calendar-nav(type="button" aria-label="Previous month" onclick!="{prevMonth}")
|
|
4
|
+
Icon(name="lucide:chevron-left" size="size-4")
|
|
5
|
+
button.calendar-heading-btn(type="button" aria-label="Jump to today" onclick!="{goToday}")
|
|
6
|
+
| {monthLabel}
|
|
7
|
+
button.calendar-nav(type="button" aria-label="Next month" onclick!="{nextMonth}")
|
|
8
|
+
Icon(name="lucide:chevron-right" size="size-4")
|
|
9
|
+
|
|
10
|
+
.calendar-row.calendar-weekdays
|
|
11
|
+
+each('weekdays as wd')
|
|
12
|
+
.calendar-weekday {wd}
|
|
13
|
+
|
|
14
|
+
+each('weeks as week')
|
|
15
|
+
.calendar-row
|
|
16
|
+
+each('week as cell')
|
|
17
|
+
button.calendar-day(
|
|
18
|
+
type="button"
|
|
19
|
+
class!="{cellClass(cell)}"
|
|
20
|
+
disabled!="{cell.disabled}"
|
|
21
|
+
aria-pressed!="{cell.selected}"
|
|
22
|
+
tabindex!="{cell.outside ? -1 : 0}"
|
|
23
|
+
onclick!="{() => onSelect(cell)}"
|
|
24
|
+
onmouseenter!="{() => range && (hovered = cell.iso)}"
|
|
25
|
+
)
|
|
26
|
+
| {cell.day}
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
30
|
+
import Icon from './Icon.n'
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
value = $bindable('') // ISO YYYY-MM-DD (single-date mode)
|
|
34
|
+
rangeValue = $bindable({ start: '', end: '' }) // (range mode)
|
|
35
|
+
range = false
|
|
36
|
+
min = ''
|
|
37
|
+
max = ''
|
|
38
|
+
label = 'Calendar'
|
|
39
|
+
onchange
|
|
40
|
+
class: extra = ''
|
|
41
|
+
} .= $props()
|
|
42
|
+
|
|
43
|
+
classes := cn 'calendar', extra
|
|
44
|
+
|
|
45
|
+
weekdays := ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
46
|
+
monthFmt := new Intl.DateTimeFormat undefined, { month: 'long', year: 'numeric' }
|
|
47
|
+
|
|
48
|
+
// View anchor — initialised from value (or today) once.
|
|
49
|
+
initial := =>
|
|
50
|
+
s := range ? rangeValue?.start : value
|
|
51
|
+
s ? new Date(`${s}T00:00:00`) : new Date()
|
|
52
|
+
anchor .= $state initial()
|
|
53
|
+
|
|
54
|
+
hovered .= $state ''
|
|
55
|
+
|
|
56
|
+
monthLabel := $derived monthFmt.format(anchor)
|
|
57
|
+
|
|
58
|
+
prevMonth := =>
|
|
59
|
+
d := new Date anchor
|
|
60
|
+
d.setMonth d.getMonth() - 1
|
|
61
|
+
anchor = d
|
|
62
|
+
|
|
63
|
+
nextMonth := =>
|
|
64
|
+
d := new Date anchor
|
|
65
|
+
d.setMonth d.getMonth() + 1
|
|
66
|
+
anchor = d
|
|
67
|
+
|
|
68
|
+
goToday := => anchor = new Date()
|
|
69
|
+
|
|
70
|
+
pad := (n) => String(n).padStart 2, '0'
|
|
71
|
+
iso := (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
|
72
|
+
todayIso := iso new Date()
|
|
73
|
+
|
|
74
|
+
weeks := $derived.by =>
|
|
75
|
+
year := anchor.getFullYear()
|
|
76
|
+
month := anchor.getMonth()
|
|
77
|
+
firstDow := new Date(year, month, 1).getDay()
|
|
78
|
+
daysInMonth := new Date(year, month + 1, 0).getDate()
|
|
79
|
+
|
|
80
|
+
// Build a flat 42-cell grid (6 rows × 7 cols).
|
|
81
|
+
cells := []
|
|
82
|
+
// leading days from previous month
|
|
83
|
+
i .= 0
|
|
84
|
+
while i < firstDow
|
|
85
|
+
d := new Date(year, month, -(firstDow - 1 - i))
|
|
86
|
+
cells.push buildCell(d, true)
|
|
87
|
+
i++
|
|
88
|
+
// current month
|
|
89
|
+
j .= 1
|
|
90
|
+
while j <= daysInMonth
|
|
91
|
+
d := new Date(year, month, j)
|
|
92
|
+
cells.push buildCell(d, false)
|
|
93
|
+
j++
|
|
94
|
+
// trailing
|
|
95
|
+
k .= 1
|
|
96
|
+
while cells.length < 42
|
|
97
|
+
d := new Date(year, month + 1, k)
|
|
98
|
+
cells.push buildCell(d, true)
|
|
99
|
+
k++
|
|
100
|
+
|
|
101
|
+
// Group into 6 rows of 7
|
|
102
|
+
out := []
|
|
103
|
+
w .= 0
|
|
104
|
+
while w < 6
|
|
105
|
+
out.push cells.slice(w * 7, w * 7 + 7)
|
|
106
|
+
w++
|
|
107
|
+
out
|
|
108
|
+
|
|
109
|
+
buildCell := (d, outside) =>
|
|
110
|
+
isoStr := iso(d)
|
|
111
|
+
isToday := isoStr === todayIso
|
|
112
|
+
disabled .= false
|
|
113
|
+
disabled = true if min && isoStr < min
|
|
114
|
+
disabled = true if max && isoStr > max
|
|
115
|
+
selected .= false
|
|
116
|
+
inRange .= false
|
|
117
|
+
if range
|
|
118
|
+
s := rangeValue?.start ?? ''
|
|
119
|
+
e := rangeValue?.end ?? ''
|
|
120
|
+
selected = !!((s && s === isoStr) || (e && e === isoStr))
|
|
121
|
+
if s && !e && hovered
|
|
122
|
+
lo := s < hovered ? s : hovered
|
|
123
|
+
hi := s < hovered ? hovered : s
|
|
124
|
+
inRange = isoStr > lo && isoStr < hi
|
|
125
|
+
else if s && e
|
|
126
|
+
inRange = isoStr > s && isoStr < e
|
|
127
|
+
else
|
|
128
|
+
selected = value === isoStr
|
|
129
|
+
{ day: d.getDate(), iso: isoStr, outside, disabled, selected, today: isToday, inRange }
|
|
130
|
+
|
|
131
|
+
cellClass := (cell) =>
|
|
132
|
+
cn
|
|
133
|
+
cell.outside && 'calendar-day-outside'
|
|
134
|
+
cell.today && 'calendar-day-today'
|
|
135
|
+
cell.selected && 'calendar-day-selected'
|
|
136
|
+
cell.inRange && 'calendar-day-in-range'
|
|
137
|
+
|
|
138
|
+
onSelect := (cell) =>
|
|
139
|
+
return if cell.disabled
|
|
140
|
+
if range
|
|
141
|
+
s := rangeValue?.start ?? ''
|
|
142
|
+
e := rangeValue?.end ?? ''
|
|
143
|
+
if !s || (s && e)
|
|
144
|
+
rangeValue = { start: cell.iso, end: '' }
|
|
145
|
+
else if cell.iso < s
|
|
146
|
+
rangeValue = { start: cell.iso, end: s }
|
|
147
|
+
else
|
|
148
|
+
rangeValue = { start: s, end: cell.iso }
|
|
149
|
+
onchange?.(rangeValue)
|
|
150
|
+
else
|
|
151
|
+
value = cell.iso
|
|
152
|
+
onchange?.(value)
|
|
153
|
+
</script>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
+snippet('body')
|
|
2
|
+
+if('header')
|
|
3
|
+
.card-header
|
|
4
|
+
| {@render header()}
|
|
5
|
+
.card-body(class!="{padded ? '' : 'p-0'}")
|
|
6
|
+
| {@render children?.()}
|
|
7
|
+
+if('footer')
|
|
8
|
+
.card-footer
|
|
9
|
+
| {@render footer()}
|
|
10
|
+
|
|
11
|
+
+if('href')
|
|
12
|
+
a(href!="{href}" target!="{target}" rel!="{rel}" class!="{classes}")
|
|
13
|
+
| {@render body()}
|
|
14
|
+
+if('!href')
|
|
15
|
+
div(class!="{classes}")
|
|
16
|
+
| {@render body()}
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
padded = true
|
|
23
|
+
interactive = false
|
|
24
|
+
href
|
|
25
|
+
target
|
|
26
|
+
rel
|
|
27
|
+
header
|
|
28
|
+
footer
|
|
29
|
+
children
|
|
30
|
+
class: extra = ''
|
|
31
|
+
} := $props()
|
|
32
|
+
|
|
33
|
+
classes := cn 'card', (interactive || href) && 'card-interactive', extra
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
.carousel(class!="{classes}" onmouseenter!="{pause}" onmouseleave!="{resume}")
|
|
2
|
+
.carousel-track(style!="{trackStyle}")
|
|
3
|
+
+each('slides as item, i')
|
|
4
|
+
.carousel-slide
|
|
5
|
+
+if('item.src')
|
|
6
|
+
img(src!="{item.src}" alt!="{item.alt ?? ''}" loading="lazy" class="block w-full h-full object-cover")
|
|
7
|
+
+if('!item.src && children')
|
|
8
|
+
| {@render children(item, i)}
|
|
9
|
+
+if('slides.length > 1')
|
|
10
|
+
button.carousel-arrow.carousel-arrow-prev(
|
|
11
|
+
type="button"
|
|
12
|
+
aria-label="Previous"
|
|
13
|
+
onclick!="{prev}"
|
|
14
|
+
)
|
|
15
|
+
Icon(name="lucide:chevron-left" size="size-5")
|
|
16
|
+
button.carousel-arrow.carousel-arrow-next(
|
|
17
|
+
type="button"
|
|
18
|
+
aria-label="Next"
|
|
19
|
+
onclick!="{next}"
|
|
20
|
+
)
|
|
21
|
+
Icon(name="lucide:chevron-right" size="size-5")
|
|
22
|
+
.carousel-dots
|
|
23
|
+
+each('slides as _, i')
|
|
24
|
+
button.carousel-dot(
|
|
25
|
+
type="button"
|
|
26
|
+
aria-label!="{`Go to slide ${i + 1}`}"
|
|
27
|
+
class!="{i === index ? 'carousel-dot-active' : ''}"
|
|
28
|
+
onclick!="{() => index = i}"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
<script>
|
|
32
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
33
|
+
import Icon from './Icon.n'
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
slides = []
|
|
37
|
+
index = $bindable(0)
|
|
38
|
+
autoplay = false
|
|
39
|
+
interval = 5000
|
|
40
|
+
aspect = 'video'
|
|
41
|
+
children
|
|
42
|
+
class: extra = ''
|
|
43
|
+
} .= $props()
|
|
44
|
+
|
|
45
|
+
// Default aspect ratios; user can pass `class="aspect-square"` etc. to override.
|
|
46
|
+
aspectClass := aspect === 'square' ? 'aspect-square'
|
|
47
|
+
: aspect === 'wide' ? 'aspect-[21/9]'
|
|
48
|
+
: aspect === 'video' ? 'aspect-video'
|
|
49
|
+
: ''
|
|
50
|
+
|
|
51
|
+
classes := cn 'carousel', aspectClass, extra
|
|
52
|
+
|
|
53
|
+
trackStyle := $derived `transform: translateX(-${index * 100}%)`
|
|
54
|
+
|
|
55
|
+
prev := => index = (index - 1 + slides.length) % slides.length
|
|
56
|
+
next := => index = (index + 1) % slides.length
|
|
57
|
+
|
|
58
|
+
paused .= $state false
|
|
59
|
+
pause := => paused = true
|
|
60
|
+
resume := => paused = false
|
|
61
|
+
|
|
62
|
+
$effect =>
|
|
63
|
+
return unless autoplay && slides.length > 1
|
|
64
|
+
id := setInterval (=> next() unless paused), interval
|
|
65
|
+
=> clearInterval id
|
|
66
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
span(class!="{classes}")
|
|
2
|
+
+if('icon')
|
|
3
|
+
Icon(name!="{icon}" size="size-3.5")
|
|
4
|
+
| {@render children?.()}
|
|
5
|
+
+if('removable')
|
|
6
|
+
button.chip-remove(
|
|
7
|
+
type="button"
|
|
8
|
+
aria-label="Remove"
|
|
9
|
+
onclick!="{onremove}"
|
|
10
|
+
)
|
|
11
|
+
Icon(name="lucide:x" size="size-3")
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
15
|
+
import Icon from './Icon.n'
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
removable = false
|
|
19
|
+
onremove = undefined
|
|
20
|
+
icon
|
|
21
|
+
children
|
|
22
|
+
class: extra = ''
|
|
23
|
+
...rest
|
|
24
|
+
} := $props()
|
|
25
|
+
|
|
26
|
+
classes := cn 'chip', extra
|
|
27
|
+
</script>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
CollapsibleRoot(bind:open class!="{classes}")
|
|
2
|
+
CollapsibleTrigger.collapsible-trigger
|
|
3
|
+
.collapsible-chevron(class!="{open ? 'collapsible-chevron-open' : ''}")
|
|
4
|
+
Icon(name="lucide:chevron-right" size="size-4")
|
|
5
|
+
+if('title')
|
|
6
|
+
span.collapsible-title {title}
|
|
7
|
+
+if('!title && trigger')
|
|
8
|
+
| {@render trigger()}
|
|
9
|
+
CollapsibleContent.collapsible-content
|
|
10
|
+
| {@render children?.()}
|
|
11
|
+
|
|
12
|
+
<script>
|
|
13
|
+
import { Collapsible } from 'bits-ui'
|
|
14
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
15
|
+
import Icon from './Icon.n'
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
Root: CollapsibleRoot
|
|
19
|
+
Trigger: CollapsibleTrigger
|
|
20
|
+
Content: CollapsibleContent
|
|
21
|
+
} := Collapsible
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
open = $bindable(false)
|
|
25
|
+
title
|
|
26
|
+
trigger
|
|
27
|
+
children
|
|
28
|
+
class: extra = ''
|
|
29
|
+
} .= $props()
|
|
30
|
+
|
|
31
|
+
classes := cn 'collapsible', extra
|
|
32
|
+
</script>
|
|
@@ -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,48 @@
|
|
|
1
|
+
ContextMenuRoot
|
|
2
|
+
ContextMenuTrigger(class!="{triggerClass ?? ''}")
|
|
3
|
+
| {@render trigger?.()}
|
|
4
|
+
ContextMenuPortal
|
|
5
|
+
ContextMenuContent.dropdown-content
|
|
6
|
+
| {@render renderItems(items)}
|
|
7
|
+
|
|
8
|
+
+snippet('renderItems', list)
|
|
9
|
+
+each('list as item')
|
|
10
|
+
+if('item.separator')
|
|
11
|
+
ContextMenuSeparator.dropdown-separator
|
|
12
|
+
+if('!item.separator && !item.children')
|
|
13
|
+
ContextMenuItem.dropdown-item(disabled!="{item.disabled}" onSelect!="{item.onSelect}")
|
|
14
|
+
+if('item.icon')
|
|
15
|
+
Icon(name!="{item.icon}" size="size-3.5")
|
|
16
|
+
span {item.label}
|
|
17
|
+
+if('!item.separator && item.children')
|
|
18
|
+
ContextMenuSubRoot
|
|
19
|
+
ContextMenuSubTrigger.dropdown-item(disabled!="{item.disabled}")
|
|
20
|
+
+if('item.icon')
|
|
21
|
+
Icon(name!="{item.icon}" size="size-3.5")
|
|
22
|
+
span.flex-1 {item.label}
|
|
23
|
+
Icon(name="lucide:chevron-right" size="size-3.5")
|
|
24
|
+
ContextMenuSubContent.dropdown-content
|
|
25
|
+
| {@render renderItems(item.children)}
|
|
26
|
+
|
|
27
|
+
<script>
|
|
28
|
+
import { ContextMenu } from 'bits-ui'
|
|
29
|
+
import Icon from './Icon.n'
|
|
30
|
+
|
|
31
|
+
{
|
|
32
|
+
Root: ContextMenuRoot
|
|
33
|
+
Trigger: ContextMenuTrigger
|
|
34
|
+
Portal: ContextMenuPortal
|
|
35
|
+
Content: ContextMenuContent
|
|
36
|
+
Item: ContextMenuItem
|
|
37
|
+
Separator: ContextMenuSeparator
|
|
38
|
+
Sub: ContextMenuSubRoot
|
|
39
|
+
SubTrigger: ContextMenuSubTrigger
|
|
40
|
+
SubContent: ContextMenuSubContent
|
|
41
|
+
} := ContextMenu
|
|
42
|
+
|
|
43
|
+
{
|
|
44
|
+
items = []
|
|
45
|
+
trigger
|
|
46
|
+
triggerClass
|
|
47
|
+
} := $props()
|
|
48
|
+
</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>
|