@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,56 @@
|
|
|
1
|
+
ComboboxRoot(bind:value bind:open type="multiple")
|
|
2
|
+
.combobox-input-wrap
|
|
3
|
+
ComboboxInput.input.combobox-input(
|
|
4
|
+
placeholder!="{placeholder}"
|
|
5
|
+
disabled!="{disabled}"
|
|
6
|
+
oninput!="{(e) => term = e.currentTarget.value}"
|
|
7
|
+
)
|
|
8
|
+
ComboboxTrigger.combobox-trigger(aria-label="Toggle options")
|
|
9
|
+
Icon(name="lucide:chevrons-up-down" size="size-4")
|
|
10
|
+
+if('value.length > 0')
|
|
11
|
+
.flex.flex-wrap.gap-1.mt-2
|
|
12
|
+
+each('value as v')
|
|
13
|
+
span.chip
|
|
14
|
+
| {labelOf(v)}
|
|
15
|
+
ComboboxPortal
|
|
16
|
+
ComboboxContent.dropdown-content
|
|
17
|
+
ComboboxViewport
|
|
18
|
+
+each('filtered as item')
|
|
19
|
+
ComboboxItem.dropdown-item(value!="{item.value}" label!="{item.label}")
|
|
20
|
+
Icon(name="lucide:check" size="size-3.5" class="opacity-0 data-[selected]:opacity-100")
|
|
21
|
+
span {item.label}
|
|
22
|
+
+if('filtered.length === 0')
|
|
23
|
+
.dropdown-item(class="text-fg-muted")
|
|
24
|
+
| No matches
|
|
25
|
+
|
|
26
|
+
<script>
|
|
27
|
+
import { Combobox } from 'bits-ui'
|
|
28
|
+
import Icon from './Icon.n'
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
Root: ComboboxRoot
|
|
32
|
+
Input: ComboboxInput
|
|
33
|
+
Trigger: ComboboxTrigger
|
|
34
|
+
Portal: ComboboxPortal
|
|
35
|
+
Content: ComboboxContent
|
|
36
|
+
Viewport: ComboboxViewport
|
|
37
|
+
Item: ComboboxItem
|
|
38
|
+
} := Combobox
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
items = []
|
|
42
|
+
value = $bindable([])
|
|
43
|
+
open = $bindable(false)
|
|
44
|
+
placeholder = 'Search…'
|
|
45
|
+
disabled = false
|
|
46
|
+
} .= $props()
|
|
47
|
+
|
|
48
|
+
term .= $state ''
|
|
49
|
+
|
|
50
|
+
filtered := $derived.by =>
|
|
51
|
+
return items unless term
|
|
52
|
+
t := term.toLowerCase()
|
|
53
|
+
items.filter (it) => it.label.toLowerCase().includes(t)
|
|
54
|
+
|
|
55
|
+
labelOf := (v) => items.find((it) => it.value === v)?.label ?? v
|
|
56
|
+
</script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
.number-input(class!="{wrapClasses}")
|
|
2
|
+
button.number-step(type="button" aria-label="Decrement" disabled!="{disabled || atMin}" onclick!="{() => step(-1)}")
|
|
3
|
+
Icon(name="lucide:minus" size="size-3.5")
|
|
4
|
+
input.input.number-input-field(
|
|
5
|
+
type="number"
|
|
6
|
+
bind:value
|
|
7
|
+
min!="{min}"
|
|
8
|
+
max!="{max}"
|
|
9
|
+
step!="{stepSize}"
|
|
10
|
+
disabled!="{disabled}"
|
|
11
|
+
readonly!="{readonly}"
|
|
12
|
+
name!="{name}"
|
|
13
|
+
id!="{resolvedId}"
|
|
14
|
+
aria-invalid!="{hasError ? 'true' : undefined}"
|
|
15
|
+
)
|
|
16
|
+
button.number-step(type="button" aria-label="Increment" disabled!="{disabled || atMax}" onclick!="{() => step(1)}")
|
|
17
|
+
Icon(name="lucide:plus" size="size-3.5")
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
21
|
+
import { getContext } from 'svelte'
|
|
22
|
+
import Icon from './Icon.n'
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
value = $bindable(0)
|
|
26
|
+
min = -Infinity
|
|
27
|
+
max = Infinity
|
|
28
|
+
stepSize = 1
|
|
29
|
+
disabled = false
|
|
30
|
+
readonly = false
|
|
31
|
+
name
|
|
32
|
+
id: providedId
|
|
33
|
+
error: explicitError = false
|
|
34
|
+
class: extra = ''
|
|
35
|
+
} .= $props()
|
|
36
|
+
|
|
37
|
+
field := getContext 'norns-ui:field'
|
|
38
|
+
resolvedId := providedId ?? field?.id
|
|
39
|
+
hasError := explicitError || field?.hasError || false
|
|
40
|
+
|
|
41
|
+
atMin := $derived value <= min
|
|
42
|
+
atMax := $derived value >= max
|
|
43
|
+
|
|
44
|
+
step := (dir) =>
|
|
45
|
+
next := (value ?? 0) + dir * stepSize
|
|
46
|
+
value = Math.max min, Math.min(max, next)
|
|
47
|
+
|
|
48
|
+
wrapClasses := cn 'number-input-wrap', extra
|
|
49
|
+
</script>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
.otp-field(class!="{classes}" role="group" aria-label!="{label}")
|
|
2
|
+
+each('cells as _, i')
|
|
3
|
+
input.otp-cell(
|
|
4
|
+
type!="{mask ? 'password' : 'text'}"
|
|
5
|
+
inputmode="numeric"
|
|
6
|
+
maxlength="1"
|
|
7
|
+
autocomplete="one-time-code"
|
|
8
|
+
pattern="[0-9]*"
|
|
9
|
+
value!="{cells[i]}"
|
|
10
|
+
oninput!="{(e) => onCellInput(e, i)}"
|
|
11
|
+
onkeydown!="{(e) => onCellKey(e, i)}"
|
|
12
|
+
onpaste!="{onPaste}"
|
|
13
|
+
data-otp-index!="{i}"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
<script>
|
|
17
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
value = $bindable('')
|
|
21
|
+
length = 6
|
|
22
|
+
mask = false
|
|
23
|
+
label = 'One-time code'
|
|
24
|
+
oncomplete
|
|
25
|
+
class: extra = ''
|
|
26
|
+
} .= $props()
|
|
27
|
+
|
|
28
|
+
cells := $derived.by =>
|
|
29
|
+
out := []
|
|
30
|
+
for i of [0...length]
|
|
31
|
+
out.push value[i] ?? ''
|
|
32
|
+
out
|
|
33
|
+
|
|
34
|
+
classes := cn 'otp-field-wrap', extra
|
|
35
|
+
|
|
36
|
+
setCell := (i, ch) =>
|
|
37
|
+
next := cells.slice()
|
|
38
|
+
next[i] = ch
|
|
39
|
+
value = next.join('').replace /\s/g, ''
|
|
40
|
+
oncomplete?.(value) if value.length === length
|
|
41
|
+
|
|
42
|
+
focusCell := (i) =>
|
|
43
|
+
el := document.querySelector `[data-otp-index="${i}"]`
|
|
44
|
+
el?.focus()
|
|
45
|
+
el?.select?.()
|
|
46
|
+
|
|
47
|
+
onCellInput := (e, i) =>
|
|
48
|
+
ch := e.target.value.replace(/[^0-9]/g, '').slice(-1)
|
|
49
|
+
setCell i, ch
|
|
50
|
+
focusCell(i + 1) if ch && i < length - 1
|
|
51
|
+
|
|
52
|
+
onCellKey := (e, i) =>
|
|
53
|
+
if e.key === 'Backspace' && !cells[i] && i > 0
|
|
54
|
+
focusCell(i - 1)
|
|
55
|
+
else if e.key === 'ArrowLeft' && i > 0
|
|
56
|
+
focusCell(i - 1)
|
|
57
|
+
else if e.key === 'ArrowRight' && i < length - 1
|
|
58
|
+
focusCell(i + 1)
|
|
59
|
+
|
|
60
|
+
onPaste := (e) =>
|
|
61
|
+
text := (e.clipboardData?.getData('text') ?? '').replace(/\D/g, '').slice(0, length)
|
|
62
|
+
return unless text
|
|
63
|
+
e.preventDefault()
|
|
64
|
+
value = text.padEnd(length, ' ').slice(0, length).trimEnd()
|
|
65
|
+
oncomplete?.(value) if value.length === length
|
|
66
|
+
focusCell Math.min(text.length, length - 1)
|
|
67
|
+
</script>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
nav.pagination(aria-label="Pagination" class!="{extra}")
|
|
2
|
+
button.pagination-item(
|
|
3
|
+
type="button"
|
|
4
|
+
aria-label="First"
|
|
5
|
+
disabled!="{page <= 1}"
|
|
6
|
+
onclick!="{() => go(1)}"
|
|
7
|
+
)
|
|
8
|
+
Icon(name="lucide:chevrons-left" size="size-4")
|
|
9
|
+
button.pagination-item(
|
|
10
|
+
type="button"
|
|
11
|
+
aria-label="Previous"
|
|
12
|
+
disabled!="{page <= 1}"
|
|
13
|
+
onclick!="{() => go(page - 1)}"
|
|
14
|
+
)
|
|
15
|
+
Icon(name="lucide:chevron-left" size="size-4")
|
|
16
|
+
+each('pages as p')
|
|
17
|
+
+if('p === "ellipsis"')
|
|
18
|
+
span.pagination-ellipsis …
|
|
19
|
+
+if('p !== "ellipsis"')
|
|
20
|
+
button.pagination-item(
|
|
21
|
+
type="button"
|
|
22
|
+
class!="{p === page ? 'pagination-item-active' : ''}"
|
|
23
|
+
aria-current!="{p === page ? 'page' : undefined}"
|
|
24
|
+
onclick!="{() => go(p)}"
|
|
25
|
+
)
|
|
26
|
+
| {p}
|
|
27
|
+
button.pagination-item(
|
|
28
|
+
type="button"
|
|
29
|
+
aria-label="Next"
|
|
30
|
+
disabled!="{page >= totalPages}"
|
|
31
|
+
onclick!="{() => go(page + 1)}"
|
|
32
|
+
)
|
|
33
|
+
Icon(name="lucide:chevron-right" size="size-4")
|
|
34
|
+
button.pagination-item(
|
|
35
|
+
type="button"
|
|
36
|
+
aria-label="Last"
|
|
37
|
+
disabled!="{page >= totalPages}"
|
|
38
|
+
onclick!="{() => go(totalPages)}"
|
|
39
|
+
)
|
|
40
|
+
Icon(name="lucide:chevrons-right" size="size-4")
|
|
41
|
+
|
|
42
|
+
<script>
|
|
43
|
+
import Icon from './Icon.n'
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
page = $bindable(1)
|
|
47
|
+
total = 0
|
|
48
|
+
pageSize = 10
|
|
49
|
+
siblingCount = 1
|
|
50
|
+
class: extra = ''
|
|
51
|
+
...rest
|
|
52
|
+
} .= $props()
|
|
53
|
+
|
|
54
|
+
totalPages := $derived Math.max(1, Math.ceil(total / pageSize))
|
|
55
|
+
|
|
56
|
+
go := (p) =>
|
|
57
|
+
clamped := Math.max 1, Math.min(totalPages, p)
|
|
58
|
+
page = clamped
|
|
59
|
+
|
|
60
|
+
pages := $derived.by =>
|
|
61
|
+
out := []
|
|
62
|
+
first := 1
|
|
63
|
+
last := totalPages
|
|
64
|
+
left := Math.max(first, page - siblingCount)
|
|
65
|
+
right := Math.min(last, page + siblingCount)
|
|
66
|
+
out.push first
|
|
67
|
+
out.push 'ellipsis' if left > first + 1
|
|
68
|
+
for n of [first + 1 .. last - 1]
|
|
69
|
+
out.push n if n >= left && n <= right
|
|
70
|
+
out.push 'ellipsis' if right < last - 1
|
|
71
|
+
out.push last unless last === first
|
|
72
|
+
out
|
|
73
|
+
</script>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
PopoverRoot(bind:open)
|
|
2
|
+
+if('trigger')
|
|
3
|
+
PopoverTrigger(class!="{triggerClass ?? undefined}")
|
|
4
|
+
| {@render trigger()}
|
|
5
|
+
PopoverPortal
|
|
6
|
+
PopoverContent(class!="{contentClasses}" side!="{side}" align!="{align}" sideOffset!="{sideOffset}")
|
|
7
|
+
| {@render children?.()}
|
|
8
|
+
|
|
9
|
+
<script>
|
|
10
|
+
import { Popover } from 'bits-ui'
|
|
11
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
Root: PopoverRoot
|
|
15
|
+
Trigger: PopoverTrigger
|
|
16
|
+
Portal: PopoverPortal
|
|
17
|
+
Content: PopoverContent
|
|
18
|
+
} := Popover
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
open = $bindable(false)
|
|
22
|
+
side = 'bottom'
|
|
23
|
+
align = 'center'
|
|
24
|
+
sideOffset = 8
|
|
25
|
+
trigger
|
|
26
|
+
children
|
|
27
|
+
triggerClass
|
|
28
|
+
class: extra = ''
|
|
29
|
+
} .= $props()
|
|
30
|
+
|
|
31
|
+
contentClasses := cn 'popover-content', extra
|
|
32
|
+
</script>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
LinkPreviewRoot(openDelay!="{openDelay}")
|
|
2
|
+
LinkPreviewTrigger(href!="{href}" class!="{triggerClass ?? ''}")
|
|
3
|
+
| {@render trigger?.()}
|
|
4
|
+
LinkPreviewContent.popover-content(side!="{side}" align!="{align}" sideOffset!="{sideOffset}" class!="{contentClass}")
|
|
5
|
+
| {@render children?.()}
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
import { LinkPreview } from 'bits-ui'
|
|
9
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
Root: LinkPreviewRoot
|
|
13
|
+
Trigger: LinkPreviewTrigger
|
|
14
|
+
Content: LinkPreviewContent
|
|
15
|
+
} := LinkPreview
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
href
|
|
19
|
+
side = 'top'
|
|
20
|
+
align = 'center'
|
|
21
|
+
sideOffset = 8
|
|
22
|
+
openDelay = 700
|
|
23
|
+
trigger
|
|
24
|
+
children
|
|
25
|
+
triggerClass
|
|
26
|
+
class: extra = ''
|
|
27
|
+
} := $props()
|
|
28
|
+
|
|
29
|
+
contentClass := cn 'w-72 p-3', extra
|
|
30
|
+
</script>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
div(
|
|
2
|
+
class!="{wrapClasses}"
|
|
3
|
+
role="progressbar"
|
|
4
|
+
aria-valuemin="0"
|
|
5
|
+
aria-valuemax!="{max}"
|
|
6
|
+
aria-valuenow!="{indeterminate ? undefined : value}"
|
|
7
|
+
)
|
|
8
|
+
.progress-bar(class!="{barClasses}" style!="{barStyle}")
|
|
9
|
+
|
|
10
|
+
<script>
|
|
11
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
value = 0
|
|
15
|
+
max = 100
|
|
16
|
+
variant = 'primary'
|
|
17
|
+
indeterminate = false
|
|
18
|
+
class: extra = ''
|
|
19
|
+
...rest
|
|
20
|
+
} := $props()
|
|
21
|
+
|
|
22
|
+
pct := $derived Math.max(0, Math.min(100, (value / max) * 100))
|
|
23
|
+
wrapClasses := cn 'progress', indeterminate && 'progress-indeterminate', extra
|
|
24
|
+
barClasses := cn variant !== 'primary' && `progress-bar-${variant}`
|
|
25
|
+
barStyle := $derived indeterminate ? '' : `width: ${pct}%`
|
|
26
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
svg(
|
|
2
|
+
class!="{classes}"
|
|
3
|
+
width!="{px}"
|
|
4
|
+
height!="{px}"
|
|
5
|
+
viewBox!="0 0 {viewBox} {viewBox}"
|
|
6
|
+
role="progressbar"
|
|
7
|
+
aria-valuemin="0"
|
|
8
|
+
aria-valuemax!="{max}"
|
|
9
|
+
aria-valuenow!="{indeterminate ? undefined : value}"
|
|
10
|
+
)
|
|
11
|
+
circle(
|
|
12
|
+
cx!="{center}"
|
|
13
|
+
cy!="{center}"
|
|
14
|
+
r!="{radius}"
|
|
15
|
+
stroke="var(--color-border)"
|
|
16
|
+
stroke-width!="{stroke}"
|
|
17
|
+
fill="none"
|
|
18
|
+
)
|
|
19
|
+
circle(
|
|
20
|
+
cx!="{center}"
|
|
21
|
+
cy!="{center}"
|
|
22
|
+
r!="{radius}"
|
|
23
|
+
stroke="var(--color-primary-600)"
|
|
24
|
+
stroke-width!="{stroke}"
|
|
25
|
+
stroke-linecap="round"
|
|
26
|
+
fill="none"
|
|
27
|
+
stroke-dasharray!="{circumference}"
|
|
28
|
+
stroke-dashoffset!="{indeterminate ? circumference * 0.7 : circumference - (circumference * pct) / 100}"
|
|
29
|
+
transform!="rotate(-90 {center} {center})"
|
|
30
|
+
style="transition: stroke-dashoffset 300ms ease-out"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
<script>
|
|
34
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
value = 0
|
|
38
|
+
max = 100
|
|
39
|
+
size = 'md'
|
|
40
|
+
indeterminate = false
|
|
41
|
+
class: extra = ''
|
|
42
|
+
} := $props()
|
|
43
|
+
|
|
44
|
+
px := size === 'sm' ? 24 : size === 'lg' ? 64 : 40
|
|
45
|
+
stroke := size === 'sm' ? 3 : size === 'lg' ? 5 : 4
|
|
46
|
+
viewBox := px
|
|
47
|
+
center := px / 2
|
|
48
|
+
radius := center - stroke
|
|
49
|
+
circumference := $derived 2 * Math.PI * radius
|
|
50
|
+
pct := $derived Math.max(0, Math.min(100, (value / max) * 100))
|
|
51
|
+
classes := cn indeterminate && 'animate-spin', extra
|
|
52
|
+
</script>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
TooltipProvider(delayDuration!="{openDelay}")
|
|
2
|
+
TooltipRoot
|
|
3
|
+
TooltipTrigger(class!="{triggerClass ?? ''}")
|
|
4
|
+
| {@render trigger?.()}
|
|
5
|
+
TooltipPortal
|
|
6
|
+
TooltipContent.popover-content(side!="{side}" align!="{align}" sideOffset!="{sideOffset}" class!="{contentClass}")
|
|
7
|
+
| {@render children?.()}
|
|
8
|
+
|
|
9
|
+
<script>
|
|
10
|
+
import { Tooltip } from 'bits-ui'
|
|
11
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
Provider: TooltipProvider
|
|
15
|
+
Root: TooltipRoot
|
|
16
|
+
Trigger: TooltipTrigger
|
|
17
|
+
Portal: TooltipPortal
|
|
18
|
+
Content: TooltipContent
|
|
19
|
+
} := Tooltip
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
side = 'top'
|
|
23
|
+
align = 'center'
|
|
24
|
+
sideOffset = 6
|
|
25
|
+
openDelay = 300
|
|
26
|
+
trigger
|
|
27
|
+
children
|
|
28
|
+
triggerClass
|
|
29
|
+
class: extra = ''
|
|
30
|
+
} := $props()
|
|
31
|
+
|
|
32
|
+
contentClass := cn 'max-w-xs p-3 text-sm', extra
|
|
33
|
+
</script>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
button(
|
|
2
|
+
type!="{type}"
|
|
3
|
+
class!="{classes}"
|
|
4
|
+
disabled!="{disabled}"
|
|
5
|
+
onclick!="{handleClick}"
|
|
6
|
+
)
|
|
7
|
+
+each('ripples as r (r.id)')
|
|
8
|
+
span.ripple-btn-ripple(
|
|
9
|
+
aria-hidden="true"
|
|
10
|
+
style!="{`left: ${r.x}px; top: ${r.y}px; width: ${r.size}px; height: ${r.size}px`}"
|
|
11
|
+
ontransitionend!="{() => removeRipple(r.id)}"
|
|
12
|
+
)
|
|
13
|
+
+if('icon')
|
|
14
|
+
Icon(name!="{icon}" size!="{iconSize}")
|
|
15
|
+
+if('children')
|
|
16
|
+
| {@render children()}
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
20
|
+
import Icon from './Icon.n'
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
variant = 'primary'
|
|
24
|
+
size = 'md'
|
|
25
|
+
type = 'button'
|
|
26
|
+
disabled = false
|
|
27
|
+
onclick
|
|
28
|
+
icon
|
|
29
|
+
children
|
|
30
|
+
class: extra = ''
|
|
31
|
+
} .= $props()
|
|
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
|
+
'ripple-btn'
|
|
40
|
+
extra
|
|
41
|
+
|
|
42
|
+
ripples .= $state []
|
|
43
|
+
nextId .= 0
|
|
44
|
+
|
|
45
|
+
handleClick := (e) =>
|
|
46
|
+
rect := e.currentTarget.getBoundingClientRect()
|
|
47
|
+
size := Math.max(rect.width, rect.height) * 2
|
|
48
|
+
x := e.clientX - rect.left - size / 2
|
|
49
|
+
y := e.clientY - rect.top - size / 2
|
|
50
|
+
id := ++nextId
|
|
51
|
+
ripples = [...ripples, { id, x, y, size }]
|
|
52
|
+
onclick?.(e)
|
|
53
|
+
|
|
54
|
+
removeRipple := (id) =>
|
|
55
|
+
ripples = ripples.filter (r) => r.id !== id
|
|
56
|
+
</script>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
ScrollAreaRoot.scroll-area(class!="{classes}" type!="{type}")
|
|
2
|
+
ScrollAreaViewport.scroll-area-viewport
|
|
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
|
|
10
|
+
|
|
11
|
+
<script>
|
|
12
|
+
import { ScrollArea } from 'bits-ui'
|
|
13
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
Root: ScrollAreaRoot
|
|
17
|
+
Viewport: ScrollAreaViewport
|
|
18
|
+
Scrollbar: ScrollAreaScrollbar
|
|
19
|
+
Thumb: ScrollAreaThumb
|
|
20
|
+
Corner: ScrollAreaCorner
|
|
21
|
+
} := ScrollArea
|
|
22
|
+
|
|
23
|
+
{
|
|
24
|
+
type = 'hover'
|
|
25
|
+
horizontal = false
|
|
26
|
+
children
|
|
27
|
+
class: extra = ''
|
|
28
|
+
} := $props()
|
|
29
|
+
|
|
30
|
+
classes := cn 'relative overflow-hidden', extra
|
|
31
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
div(role="separator" class!="{classes}" aria-orientation!="{orientation}")
|
|
2
|
+
|
|
3
|
+
<script>
|
|
4
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
5
|
+
|
|
6
|
+
{
|
|
7
|
+
orientation = 'horizontal'
|
|
8
|
+
class: extra = ''
|
|
9
|
+
} := $props()
|
|
10
|
+
|
|
11
|
+
classes := cn 'separator', `separator-${orientation}`, extra
|
|
12
|
+
</script>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
DialogRoot(bind:open)
|
|
2
|
+
+if('trigger')
|
|
3
|
+
DialogTrigger(class!="{triggerClass ?? undefined}")
|
|
4
|
+
| {@render trigger()}
|
|
5
|
+
DialogPortal
|
|
6
|
+
DialogOverlay(class!="{overlayClasses}")
|
|
7
|
+
DialogContent(class!="{contentClasses}" data-side!="{side}")
|
|
8
|
+
+if('title')
|
|
9
|
+
DialogTitle.dialog-title
|
|
10
|
+
| {title}
|
|
11
|
+
+if('description')
|
|
12
|
+
DialogDescription.dialog-description
|
|
13
|
+
| {description}
|
|
14
|
+
+if('children')
|
|
15
|
+
.sheet-body
|
|
16
|
+
| {@render children()}
|
|
17
|
+
+if('actions')
|
|
18
|
+
.dialog-actions
|
|
19
|
+
| {@render actions()}
|
|
20
|
+
+if('!hideClose')
|
|
21
|
+
DialogClose.dialog-close(aria-label="Close")
|
|
22
|
+
| ✕
|
|
23
|
+
|
|
24
|
+
<script>
|
|
25
|
+
import { Dialog } from 'bits-ui'
|
|
26
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
Root: DialogRoot
|
|
30
|
+
Trigger: DialogTrigger
|
|
31
|
+
Portal: DialogPortal
|
|
32
|
+
Overlay: DialogOverlay
|
|
33
|
+
Content: DialogContent
|
|
34
|
+
Title: DialogTitle
|
|
35
|
+
Description: DialogDescription
|
|
36
|
+
Close: DialogClose
|
|
37
|
+
} := Dialog
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
open = $bindable(false)
|
|
41
|
+
side = 'right'
|
|
42
|
+
title
|
|
43
|
+
description
|
|
44
|
+
hideClose = false
|
|
45
|
+
trigger
|
|
46
|
+
actions
|
|
47
|
+
children
|
|
48
|
+
triggerClass
|
|
49
|
+
overlayClass = ''
|
|
50
|
+
class: extra = ''
|
|
51
|
+
} .= $props()
|
|
52
|
+
|
|
53
|
+
overlayClasses := cn 'dialog-overlay', overlayClass
|
|
54
|
+
contentClasses := cn 'sheet-content', `sheet-${side}`, extra
|
|
55
|
+
</script>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
button(
|
|
2
|
+
type!="{type}"
|
|
3
|
+
class!="{classes}"
|
|
4
|
+
disabled!="{disabled}"
|
|
5
|
+
onclick!="{onclick}"
|
|
6
|
+
)
|
|
7
|
+
span.shiny-btn-shine(aria-hidden="true")
|
|
8
|
+
+if('icon')
|
|
9
|
+
Icon(name!="{icon}" size!="{iconSize}")
|
|
10
|
+
+if('children')
|
|
11
|
+
| {@render children()}
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
15
|
+
import Icon from './Icon.n'
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
variant = 'primary'
|
|
19
|
+
size = 'md'
|
|
20
|
+
type = 'button'
|
|
21
|
+
disabled = false
|
|
22
|
+
onclick
|
|
23
|
+
icon
|
|
24
|
+
children
|
|
25
|
+
class: extra = ''
|
|
26
|
+
} := $props()
|
|
27
|
+
|
|
28
|
+
iconSize := size === 'sm' ? 'size-3.5' : size === 'lg' ? 'size-5' : 'size-4'
|
|
29
|
+
|
|
30
|
+
classes := cn
|
|
31
|
+
'btn'
|
|
32
|
+
`btn-${variant}`
|
|
33
|
+
size !== 'md' && `btn-${size}`
|
|
34
|
+
'shiny-btn'
|
|
35
|
+
extra
|
|
36
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
span(class!="{classes}" style!="{style}" aria-hidden="true")
|
|
2
|
+
|
|
3
|
+
<script>
|
|
4
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
5
|
+
|
|
6
|
+
{
|
|
7
|
+
variant = 'rect'
|
|
8
|
+
width
|
|
9
|
+
height
|
|
10
|
+
class: extra = ''
|
|
11
|
+
...rest
|
|
12
|
+
} := $props()
|
|
13
|
+
|
|
14
|
+
classes := cn 'skeleton', variant === 'circle' && 'skeleton-circle', extra
|
|
15
|
+
|
|
16
|
+
style := $derived.by =>
|
|
17
|
+
parts := []
|
|
18
|
+
if width
|
|
19
|
+
parts.push `width: ${typeof width === 'number' ? `${width}px` : width}`
|
|
20
|
+
if height
|
|
21
|
+
parts.push `height: ${typeof height === 'number' ? `${height}px` : height}`
|
|
22
|
+
else if variant === 'text'
|
|
23
|
+
parts.push 'height: 0.875rem'
|
|
24
|
+
else if variant === 'circle' && !height
|
|
25
|
+
parts.push 'height: 2.5rem; width: 2.5rem'
|
|
26
|
+
parts.join '; '
|
|
27
|
+
</script>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
ol(class!="{classes}")
|
|
2
|
+
+each('steps as step, i')
|
|
3
|
+
li.stepper-step(class!="{stepClass(i)}")
|
|
4
|
+
.stepper-marker
|
|
5
|
+
+if('isComplete(i)')
|
|
6
|
+
Icon(name="lucide:check" size="size-3.5")
|
|
7
|
+
+if('!isComplete(i)')
|
|
8
|
+
| {i + 1}
|
|
9
|
+
.stepper-label
|
|
10
|
+
| {step.label}
|
|
11
|
+
+if('i < steps.length - 1')
|
|
12
|
+
.stepper-divider
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import { cn } from '@human-synthesis/norns-ui/cn'
|
|
16
|
+
import Icon from './Icon.n'
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
steps = []
|
|
20
|
+
current = 0
|
|
21
|
+
orientation = 'horizontal'
|
|
22
|
+
class: extra = ''
|
|
23
|
+
...rest
|
|
24
|
+
} := $props()
|
|
25
|
+
|
|
26
|
+
classes := cn 'stepper', orientation === 'vertical' && 'stepper-vertical', extra
|
|
27
|
+
|
|
28
|
+
isComplete := (i) => steps[i]?.complete ?? i < current
|
|
29
|
+
isCurrent := (i) => steps[i]?.current ?? i === current
|
|
30
|
+
|
|
31
|
+
stepClass := (i) =>
|
|
32
|
+
cn
|
|
33
|
+
isCurrent(i) && 'stepper-step-current'
|
|
34
|
+
isComplete(i) && 'stepper-step-complete'
|
|
35
|
+
</script>
|