@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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -129
  3. package/package.json +23 -15
  4. package/src/auto-import.js +96 -22
  5. package/src/components/Accordion.n +38 -0
  6. package/src/components/Audio.n +24 -0
  7. package/src/components/Autocomplete.n +59 -0
  8. package/src/components/Avatar.n +27 -0
  9. package/src/components/AvatarGroup.n +25 -0
  10. package/src/components/Badge.n +20 -0
  11. package/src/components/Banner.n +25 -0
  12. package/src/components/Breadcrumbs.n +22 -0
  13. package/src/components/Btn.n +29 -8
  14. package/src/components/ButtonGroup.n +14 -0
  15. package/src/components/Calendar.n +153 -0
  16. package/src/components/Card.n +34 -0
  17. package/src/components/Carousel.n +66 -0
  18. package/src/components/Chip.n +27 -0
  19. package/src/components/Collapsible.n +32 -0
  20. package/src/components/ColorPicker.n +62 -0
  21. package/src/components/ContextMenu.n +48 -0
  22. package/src/components/CopyButton.n +50 -0
  23. package/src/components/DataTable.n +61 -0
  24. package/src/components/DatePicker.n +68 -0
  25. package/src/components/DateRangePicker.n +59 -0
  26. package/src/components/Dialog.n +54 -0
  27. package/src/components/Drawer.n +49 -0
  28. package/src/components/Dropdown.n +41 -0
  29. package/src/components/Field.n +29 -20
  30. package/src/components/Form.n +22 -0
  31. package/src/components/GradientText.n +21 -0
  32. package/src/components/Header.n +23 -0
  33. package/src/components/HeroBanner.n +37 -0
  34. package/src/components/HierarchicalMenu.n +38 -0
  35. package/src/components/Icon.n +16 -0
  36. package/src/components/Image.n +31 -0
  37. package/src/components/MegaMenu.n +36 -0
  38. package/src/components/MultiSelect.n +56 -0
  39. package/src/components/NumberInput.n +49 -0
  40. package/src/components/OtpField.n +67 -0
  41. package/src/components/Pagination.n +73 -0
  42. package/src/components/Popover.n +32 -0
  43. package/src/components/PreviewCard.n +30 -0
  44. package/src/components/Progress.n +26 -0
  45. package/src/components/ProgressCircular.n +52 -0
  46. package/src/components/RichTooltip.n +33 -0
  47. package/src/components/RippleButton.n +56 -0
  48. package/src/components/ScrollArea.n +31 -0
  49. package/src/components/Separator.n +12 -0
  50. package/src/components/Sheet.n +55 -0
  51. package/src/components/ShinyButton.n +36 -0
  52. package/src/components/Skeleton.n +27 -0
  53. package/src/components/Stepper.n +35 -0
  54. package/src/components/Surface.n +10 -0
  55. package/src/components/Tabs.n +28 -0
  56. package/src/components/TagsInput.n +66 -0
  57. package/src/components/ThemeToggler.n +43 -0
  58. package/src/components/TimePicker.n +166 -0
  59. package/src/components/Timeline.n +27 -0
  60. package/src/components/ToastProvider.n +16 -0
  61. package/src/components/ToggleButton.n +29 -0
  62. package/src/components/ToggleButtonGroup.n +34 -0
  63. package/src/components/Toolbar.n +15 -0
  64. package/src/components/Tooltip.n +34 -0
  65. package/src/components/Tree.n +57 -0
  66. package/src/components/Uploader.n +88 -0
  67. package/src/components/Video.n +26 -0
  68. package/src/index.js +76 -0
  69. package/src/lib/toast.svelte.js +42 -0
  70. package/src/motion/AnimatedNumber.n +55 -0
  71. package/src/motion/GradientBackground.n +25 -0
  72. package/src/motion/LiquidButton.n +48 -0
  73. package/src/motion/Reveal.n +37 -0
  74. package/src/motion/Sparkles.n +53 -0
  75. package/src/motion/index.js +18 -0
  76. package/src/styles/atoms.css +1794 -71
  77. package/src/styles/tokens.css +171 -40
  78. package/src/types/Accordion.d.ts +17 -0
  79. package/src/types/AnimatedNumber.d.ts +15 -0
  80. package/src/types/Audio.d.ts +18 -0
  81. package/src/types/Autocomplete.d.ts +17 -0
  82. package/src/types/Avatar.d.ts +14 -0
  83. package/src/types/AvatarGroup.d.ts +15 -0
  84. package/src/types/Badge.d.ts +14 -0
  85. package/src/types/Banner.d.ts +15 -0
  86. package/src/types/Breadcrumbs.d.ts +15 -0
  87. package/src/types/Btn.d.ts +10 -0
  88. package/src/types/ButtonGroup.d.ts +10 -0
  89. package/src/types/Calendar.d.ts +20 -0
  90. package/src/types/Card.d.ts +19 -0
  91. package/src/types/Carousel.d.ts +23 -0
  92. package/src/types/Chip.d.ts +12 -0
  93. package/src/types/Collapsible.d.ts +12 -0
  94. package/src/types/ColorPicker.d.ts +15 -0
  95. package/src/types/ContextMenu.d.ts +19 -0
  96. package/src/types/CopyButton.d.ts +16 -0
  97. package/src/types/DataTable.d.ts +27 -0
  98. package/src/types/DatePicker.d.ts +18 -0
  99. package/src/types/DateRangePicker.d.ts +17 -0
  100. package/src/types/Dialog.d.ts +17 -0
  101. package/src/types/Drawer.d.ts +18 -0
  102. package/src/types/Dropdown.d.ts +23 -0
  103. package/src/types/Field.d.ts +14 -3
  104. package/src/types/Form.d.ts +17 -0
  105. package/src/types/GradientBackground.d.ts +14 -0
  106. package/src/types/GradientText.d.ts +19 -0
  107. package/src/types/Header.d.ts +12 -0
  108. package/src/types/HeroBanner.d.ts +17 -0
  109. package/src/types/HierarchicalMenu.d.ts +20 -0
  110. package/src/types/Icon.d.ts +21 -0
  111. package/src/types/Image.d.ts +15 -0
  112. package/src/types/LiquidButton.d.ts +15 -0
  113. package/src/types/MegaMenu.d.ts +29 -0
  114. package/src/types/MultiSelect.d.ts +13 -0
  115. package/src/types/NumberInput.d.ts +17 -0
  116. package/src/types/OtpField.d.ts +13 -0
  117. package/src/types/Pagination.d.ts +13 -0
  118. package/src/types/Popover.d.ts +18 -0
  119. package/src/types/PreviewCard.d.ts +16 -0
  120. package/src/types/Progress.d.ts +14 -0
  121. package/src/types/ProgressCircular.d.ts +14 -0
  122. package/src/types/Reveal.d.ts +20 -0
  123. package/src/types/RichTooltip.d.ts +16 -0
  124. package/src/types/RippleButton.d.ts +15 -0
  125. package/src/types/ScrollArea.d.ts +11 -0
  126. package/src/types/Separator.d.ts +9 -0
  127. package/src/types/Sheet.d.ts +20 -0
  128. package/src/types/ShinyButton.d.ts +15 -0
  129. package/src/types/Skeleton.d.ts +13 -0
  130. package/src/types/Sparkles.d.ts +13 -0
  131. package/src/types/Stepper.d.ts +19 -0
  132. package/src/types/Surface.d.ts +10 -0
  133. package/src/types/Tabs.d.ts +17 -0
  134. package/src/types/TagsInput.d.ts +17 -0
  135. package/src/types/ThemeToggler.d.ts +17 -0
  136. package/src/types/TimePicker.d.ts +20 -0
  137. package/src/types/Timeline.d.ts +17 -0
  138. package/src/types/ToastProvider.d.ts +15 -0
  139. package/src/types/ToggleButton.d.ts +15 -0
  140. package/src/types/ToggleButtonGroup.d.ts +20 -0
  141. package/src/types/Toolbar.d.ts +12 -0
  142. package/src/types/Tooltip.d.ts +15 -0
  143. package/src/types/Tree.d.ts +20 -0
  144. package/src/types/Uploader.d.ts +15 -0
  145. 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>
@@ -0,0 +1,10 @@
1
+ div(class!="{classes}")
2
+ | {@render children?.()}
3
+
4
+ <script>
5
+ import { cn } from '@human-synthesis/norns-ui/cn'
6
+
7
+ { tone, children, class: extra = '' } := $props()
8
+
9
+ classes := cn 'surface p-4', tone === 'glass' && 'surface-glass', extra
10
+ </script>