@human-synthesis/norns-ui 0.0.3 → 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 +73 -229
- package/package.json +7 -1
- package/src/auto-import.js +84 -19
- 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/Drawer.n +49 -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/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/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/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/ToggleButton.n +29 -0
- package/src/components/ToggleButtonGroup.n +34 -0
- package/src/components/Toolbar.n +15 -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 +66 -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 +1673 -118
- 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/Drawer.d.ts +18 -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/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/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/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/ToggleButton.d.ts +15 -0
- package/src/types/ToggleButtonGroup.d.ts +20 -0
- package/src/types/Toolbar.d.ts +12 -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,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,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,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,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>
|