@rokkit/ui 1.0.0-next.98 → 1.0.1
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/README.md +159 -1
- package/package.json +52 -53
- package/src/MarkdownRenderer.svelte +106 -0
- package/src/components/AlertList.svelte +59 -0
- package/src/components/Avatar.svelte +68 -0
- package/src/components/Badge.svelte +57 -0
- package/src/components/BreadCrumbs.svelte +94 -0
- package/src/components/Button.svelte +91 -0
- package/src/components/ButtonGroup.svelte +9 -0
- package/src/components/Card.svelte +71 -0
- package/src/components/Carousel.svelte +169 -0
- package/src/components/Code.svelte +189 -0
- package/src/components/Connector.svelte +46 -0
- package/src/components/Divider.svelte +24 -0
- package/src/components/Dropdown.svelte +202 -0
- package/src/components/FloatingAction.svelte +367 -0
- package/src/components/FloatingNavigation.svelte +256 -0
- package/src/components/Grid.svelte +128 -0
- package/src/components/ItemContent.svelte +25 -0
- package/src/components/LazyTree.svelte +171 -0
- package/src/components/List.svelte +224 -0
- package/src/components/Menu.svelte +270 -0
- package/src/components/Message.svelte +75 -0
- package/src/components/MultiSelect.svelte +367 -0
- package/src/components/PaletteManager.svelte +360 -0
- package/src/components/Pill.svelte +83 -0
- package/src/components/ProgressBar.svelte +31 -0
- package/src/components/Range.svelte +346 -0
- package/src/components/Rating.svelte +115 -0
- package/src/components/Reveal.svelte +46 -0
- package/src/components/SearchFilter.svelte +88 -0
- package/src/components/Select.svelte +424 -0
- package/src/components/Shine.svelte +87 -0
- package/src/components/Stack.svelte +38 -0
- package/src/components/StatusList.svelte +18 -0
- package/src/components/Stepper.svelte +174 -0
- package/src/components/Swatch.svelte +102 -0
- package/src/components/Switch.svelte +71 -0
- package/src/components/Table.svelte +245 -0
- package/src/components/Tabs.svelte +202 -0
- package/src/components/Tilt.svelte +68 -0
- package/src/components/Timeline.svelte +61 -0
- package/src/components/Toggle.svelte +93 -0
- package/src/components/Toolbar.svelte +306 -0
- package/src/components/ToolbarGroup.svelte +17 -0
- package/src/components/Tooltip.svelte +121 -0
- package/src/components/Tree.svelte +143 -0
- package/src/components/UploadFileStatus.svelte +102 -0
- package/src/components/UploadProgress.svelte +127 -0
- package/src/components/UploadTarget.svelte +126 -0
- package/src/components/index.ts +48 -0
- package/src/index.ts +59 -0
- package/src/markdown-plugin.ts +12 -0
- package/src/types/button.ts +86 -0
- package/src/types/code.ts +46 -0
- package/src/types/floating-action.ts +123 -0
- package/src/types/floating-navigation.ts +80 -0
- package/src/types/index.ts +58 -0
- package/src/types/list.ts +200 -0
- package/src/types/menu.ts +95 -0
- package/src/types/palette.ts +160 -0
- package/src/types/range.ts +51 -0
- package/src/types/search-filter.ts +67 -0
- package/src/types/select.ts +176 -0
- package/src/types/switch.ts +68 -0
- package/src/types/table.ts +219 -0
- package/src/types/tabs.ts +103 -0
- package/src/types/timeline.ts +53 -0
- package/src/types/toggle.ts +68 -0
- package/src/types/toolbar.ts +167 -0
- package/src/types/tooltip.ts +21 -0
- package/src/types/tree.ts +250 -0
- package/src/types/upload-file-status.ts +45 -0
- package/src/types/upload-progress.ts +111 -0
- package/src/types/upload-target.ts +71 -0
- package/src/utils/palette.ts +618 -0
- package/src/utils/shiki.ts +114 -0
- package/src/utils/upload.js +145 -0
- package/LICENSE +0 -21
- package/src/index.js +0 -8
- package/src/wrappers.js +0 -2
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ButtonProps } from '../types/button.js'
|
|
3
|
+
import { ProxyItem } from '@rokkit/states'
|
|
4
|
+
import ItemContent from './ItemContent.svelte'
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
variant = 'default',
|
|
8
|
+
style = 'default',
|
|
9
|
+
size = 'md',
|
|
10
|
+
type = 'button',
|
|
11
|
+
label,
|
|
12
|
+
icon,
|
|
13
|
+
iconRight,
|
|
14
|
+
href,
|
|
15
|
+
target = '_self',
|
|
16
|
+
title,
|
|
17
|
+
disabled = false,
|
|
18
|
+
loading = false,
|
|
19
|
+
onclick,
|
|
20
|
+
class: className = '',
|
|
21
|
+
children,
|
|
22
|
+
...rest
|
|
23
|
+
}: ButtonProps = $props()
|
|
24
|
+
|
|
25
|
+
const isIconOnly = $derived(Boolean(icon) && !label && !children)
|
|
26
|
+
const isDisabled = $derived(disabled || loading)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a ProxyItem for default content rendering.
|
|
30
|
+
* Constructs a minimal item from button props.
|
|
31
|
+
*/
|
|
32
|
+
const proxy = $derived(new ProxyItem({ label, icon, iconRight }))
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
{#snippet defaultContent()}
|
|
36
|
+
{#if loading}
|
|
37
|
+
<span data-button-spinner aria-hidden="true"></span>
|
|
38
|
+
{/if}
|
|
39
|
+
<ItemContent {proxy} />
|
|
40
|
+
{#if iconRight}
|
|
41
|
+
<span data-button-icon-right class={iconRight} aria-hidden="true"></span>
|
|
42
|
+
{/if}
|
|
43
|
+
{/snippet}
|
|
44
|
+
|
|
45
|
+
{#if href && !isDisabled}
|
|
46
|
+
<a
|
|
47
|
+
{href}
|
|
48
|
+
{target}
|
|
49
|
+
{title}
|
|
50
|
+
data-button
|
|
51
|
+
data-variant={variant}
|
|
52
|
+
data-style={style}
|
|
53
|
+
data-size={size}
|
|
54
|
+
data-icon-only={isIconOnly || undefined}
|
|
55
|
+
data-loading={loading || undefined}
|
|
56
|
+
class={className || undefined}
|
|
57
|
+
aria-label={label}
|
|
58
|
+
aria-busy={loading || undefined}
|
|
59
|
+
{...rest}
|
|
60
|
+
>
|
|
61
|
+
{#if children}
|
|
62
|
+
{@render children()}
|
|
63
|
+
{:else}
|
|
64
|
+
{@render defaultContent()}
|
|
65
|
+
{/if}
|
|
66
|
+
</a>
|
|
67
|
+
{:else}
|
|
68
|
+
<button
|
|
69
|
+
{type}
|
|
70
|
+
{title}
|
|
71
|
+
data-button
|
|
72
|
+
data-variant={variant}
|
|
73
|
+
data-style={style}
|
|
74
|
+
data-size={size}
|
|
75
|
+
data-disabled={isDisabled || undefined}
|
|
76
|
+
data-icon-only={isIconOnly || undefined}
|
|
77
|
+
data-loading={loading || undefined}
|
|
78
|
+
class={className || undefined}
|
|
79
|
+
disabled={isDisabled}
|
|
80
|
+
aria-label={label}
|
|
81
|
+
aria-busy={loading || undefined}
|
|
82
|
+
{onclick}
|
|
83
|
+
{...rest}
|
|
84
|
+
>
|
|
85
|
+
{#if children}
|
|
86
|
+
{@render children()}
|
|
87
|
+
{:else}
|
|
88
|
+
{@render defaultContent()}
|
|
89
|
+
{/if}
|
|
90
|
+
</button>
|
|
91
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ButtonGroupProps } from '../types/button.js'
|
|
3
|
+
|
|
4
|
+
const { size = 'md', class: className = '', children }: ButtonGroupProps = $props()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<div data-button-group data-size={size} class={className || undefined} role="group">
|
|
8
|
+
{@render children?.()}
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
|
|
4
|
+
interface CardProps {
|
|
5
|
+
/** Optional href to render as a link */
|
|
6
|
+
href?: string
|
|
7
|
+
/** Click handler (only applies when no href) */
|
|
8
|
+
onclick?: () => void
|
|
9
|
+
/** Visual variant */
|
|
10
|
+
variant?: 'default' | 'primary' | 'secondary' | 'tertiary'
|
|
11
|
+
/** Additional CSS class */
|
|
12
|
+
class?: string
|
|
13
|
+
/** Card header snippet */
|
|
14
|
+
header?: Snippet
|
|
15
|
+
/** Card footer snippet */
|
|
16
|
+
footer?: Snippet
|
|
17
|
+
/** Card body content */
|
|
18
|
+
children?: Snippet
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
href,
|
|
23
|
+
onclick,
|
|
24
|
+
variant = 'default',
|
|
25
|
+
class: className = '',
|
|
26
|
+
header,
|
|
27
|
+
footer,
|
|
28
|
+
children
|
|
29
|
+
}: CardProps = $props()
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
{#snippet cardContent()}
|
|
33
|
+
{#if header}
|
|
34
|
+
<div data-card-header>
|
|
35
|
+
{@render header()}
|
|
36
|
+
</div>
|
|
37
|
+
{/if}
|
|
38
|
+
|
|
39
|
+
{#if children}
|
|
40
|
+
<div data-card-body>
|
|
41
|
+
{@render children()}
|
|
42
|
+
</div>
|
|
43
|
+
{/if}
|
|
44
|
+
|
|
45
|
+
{#if footer}
|
|
46
|
+
<div data-card-footer>
|
|
47
|
+
{@render footer()}
|
|
48
|
+
</div>
|
|
49
|
+
{/if}
|
|
50
|
+
{/snippet}
|
|
51
|
+
|
|
52
|
+
{#if href}
|
|
53
|
+
<a {href} data-card data-variant={variant} class={className || undefined}>
|
|
54
|
+
{@render cardContent()}
|
|
55
|
+
</a>
|
|
56
|
+
{:else if onclick}
|
|
57
|
+
<button
|
|
58
|
+
type="button"
|
|
59
|
+
data-card
|
|
60
|
+
data-card-interactive
|
|
61
|
+
data-variant={variant}
|
|
62
|
+
class={className || undefined}
|
|
63
|
+
{onclick}
|
|
64
|
+
>
|
|
65
|
+
{@render cardContent()}
|
|
66
|
+
</button>
|
|
67
|
+
{:else}
|
|
68
|
+
<div data-card data-variant={variant} class={className || undefined}>
|
|
69
|
+
{@render cardContent()}
|
|
70
|
+
</div>
|
|
71
|
+
{/if}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import { swipeable, keyboard } from '@rokkit/actions'
|
|
4
|
+
import { messages } from '@rokkit/states'
|
|
5
|
+
|
|
6
|
+
interface CarouselProps {
|
|
7
|
+
/** Number of slides (required when using children snippet) */
|
|
8
|
+
count?: number
|
|
9
|
+
/** Current slide index (bindable) */
|
|
10
|
+
current?: number
|
|
11
|
+
/** Auto-advance slides */
|
|
12
|
+
autoplay?: boolean
|
|
13
|
+
/** Autoplay interval in milliseconds (default: 5000) */
|
|
14
|
+
interval?: number
|
|
15
|
+
/** Wrap around at ends (default: true) */
|
|
16
|
+
loop?: boolean
|
|
17
|
+
/** Show navigation dots (default: true) */
|
|
18
|
+
showDots?: boolean
|
|
19
|
+
/** Show prev/next arrow buttons (default: true) */
|
|
20
|
+
showArrows?: boolean
|
|
21
|
+
/** Transition effect (default: 'slide') */
|
|
22
|
+
transition?: 'slide' | 'fade' | 'none'
|
|
23
|
+
/** Additional CSS class */
|
|
24
|
+
class?: string
|
|
25
|
+
/** Slide content — receives (index, current) */
|
|
26
|
+
slide?: Snippet<[number, number]>
|
|
27
|
+
/** Children snippet (alternative to slide) */
|
|
28
|
+
children?: Snippet
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
count = 0,
|
|
33
|
+
current = $bindable(0),
|
|
34
|
+
autoplay = false,
|
|
35
|
+
interval = 5000,
|
|
36
|
+
loop = true,
|
|
37
|
+
showDots = true,
|
|
38
|
+
showArrows = true,
|
|
39
|
+
transition = 'slide',
|
|
40
|
+
labels: userLabels = {},
|
|
41
|
+
class: className = '',
|
|
42
|
+
slide,
|
|
43
|
+
children
|
|
44
|
+
}: CarouselProps & { labels?: Record<string, string> } = $props()
|
|
45
|
+
|
|
46
|
+
const labels = $derived({ ...messages.carousel, ...userLabels })
|
|
47
|
+
|
|
48
|
+
let hovered = $state(false)
|
|
49
|
+
|
|
50
|
+
const keyMap = {
|
|
51
|
+
prev: ['ArrowLeft'],
|
|
52
|
+
next: ['ArrowRight'],
|
|
53
|
+
first: ['Home'],
|
|
54
|
+
last: ['End']
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function prev() {
|
|
58
|
+
if (current > 0) {
|
|
59
|
+
current--
|
|
60
|
+
} else if (loop) {
|
|
61
|
+
current = count - 1
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function next() {
|
|
66
|
+
if (current < count - 1) {
|
|
67
|
+
current++
|
|
68
|
+
} else if (loop) {
|
|
69
|
+
current = 0
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function goTo(index: number) {
|
|
74
|
+
if (index >= 0 && index < count) {
|
|
75
|
+
current = index
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Autoplay
|
|
80
|
+
$effect(() => {
|
|
81
|
+
if (!autoplay || hovered || count <= 1) return
|
|
82
|
+
|
|
83
|
+
const timer = setInterval(() => {
|
|
84
|
+
next()
|
|
85
|
+
}, interval)
|
|
86
|
+
|
|
87
|
+
return () => clearInterval(timer)
|
|
88
|
+
})
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
92
|
+
<div
|
|
93
|
+
data-carousel
|
|
94
|
+
data-carousel-transition={transition}
|
|
95
|
+
class={className || undefined}
|
|
96
|
+
role="application"
|
|
97
|
+
aria-roledescription="carousel"
|
|
98
|
+
aria-label={labels.label}
|
|
99
|
+
tabindex="0"
|
|
100
|
+
use:swipeable={{ horizontal: true, vertical: false }}
|
|
101
|
+
use:keyboard={keyMap}
|
|
102
|
+
onswipeLeft={next}
|
|
103
|
+
onswipeRight={prev}
|
|
104
|
+
onprev={prev}
|
|
105
|
+
onnext={next}
|
|
106
|
+
onfirst={() => goTo(0)}
|
|
107
|
+
onlast={() => goTo(count - 1)}
|
|
108
|
+
onmouseenter={() => (hovered = true)}
|
|
109
|
+
onmouseleave={() => (hovered = false)}
|
|
110
|
+
>
|
|
111
|
+
<div data-carousel-viewport>
|
|
112
|
+
<div data-carousel-track style:--carousel-current={current} style:--carousel-count={count}>
|
|
113
|
+
{#if slide}
|
|
114
|
+
{#each Array(count) as _, index (index)}
|
|
115
|
+
<div
|
|
116
|
+
data-carousel-slide
|
|
117
|
+
data-active={index === current || undefined}
|
|
118
|
+
role="tabpanel"
|
|
119
|
+
aria-roledescription="slide"
|
|
120
|
+
aria-label="Slide {index + 1} of {count}"
|
|
121
|
+
aria-hidden={index !== current}
|
|
122
|
+
>
|
|
123
|
+
{@render slide(index, current)}
|
|
124
|
+
</div>
|
|
125
|
+
{/each}
|
|
126
|
+
{:else if children}
|
|
127
|
+
{@render children()}
|
|
128
|
+
{/if}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{#if showArrows && count > 1}
|
|
133
|
+
<button
|
|
134
|
+
data-carousel-prev
|
|
135
|
+
type="button"
|
|
136
|
+
aria-label={labels.prev}
|
|
137
|
+
onclick={prev}
|
|
138
|
+
disabled={!loop && current === 0}
|
|
139
|
+
>
|
|
140
|
+
<span class="i-lucide:chevron-left" aria-hidden="true"></span>
|
|
141
|
+
</button>
|
|
142
|
+
|
|
143
|
+
<button
|
|
144
|
+
data-carousel-next
|
|
145
|
+
type="button"
|
|
146
|
+
aria-label={labels.next}
|
|
147
|
+
onclick={next}
|
|
148
|
+
disabled={!loop && current === count - 1}
|
|
149
|
+
>
|
|
150
|
+
<span class="i-lucide:chevron-right" aria-hidden="true"></span>
|
|
151
|
+
</button>
|
|
152
|
+
{/if}
|
|
153
|
+
|
|
154
|
+
{#if showDots && count > 1}
|
|
155
|
+
<div data-carousel-dots role="tablist" aria-label={labels.slides}>
|
|
156
|
+
{#each Array(count) as _, index (index)}
|
|
157
|
+
<button
|
|
158
|
+
data-carousel-dot
|
|
159
|
+
data-active={index === current || undefined}
|
|
160
|
+
type="button"
|
|
161
|
+
role="tab"
|
|
162
|
+
aria-selected={index === current}
|
|
163
|
+
aria-label="Go to slide {index + 1}"
|
|
164
|
+
onclick={() => goTo(index)}
|
|
165
|
+
></button>
|
|
166
|
+
{/each}
|
|
167
|
+
</div>
|
|
168
|
+
{/if}
|
|
169
|
+
</div>
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { CodeProps, CodeStateIcons } from '../types/index.js'
|
|
3
|
+
import { defaultCodeStateIcons } from '../types/code.js'
|
|
4
|
+
import { highlightCode } from '../utils/shiki.js'
|
|
5
|
+
import { messages } from '@rokkit/states'
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
code,
|
|
9
|
+
language = 'text',
|
|
10
|
+
theme = 'dark',
|
|
11
|
+
showLineNumbers = false,
|
|
12
|
+
showCopyButton = true,
|
|
13
|
+
labels: userLabels = {},
|
|
14
|
+
icons: userIcons,
|
|
15
|
+
class: className = ''
|
|
16
|
+
}: CodeProps & { labels?: Record<string, string> } = $props()
|
|
17
|
+
|
|
18
|
+
const labels = $derived({ ...messages.code, ...userLabels })
|
|
19
|
+
|
|
20
|
+
// Merge icons with defaults
|
|
21
|
+
const icons = $derived<CodeStateIcons>({ ...defaultCodeStateIcons, ...userIcons })
|
|
22
|
+
|
|
23
|
+
let copied = $state(false)
|
|
24
|
+
let highlightedCode = $derived(highlightCode(code, { lang: language, theme }))
|
|
25
|
+
|
|
26
|
+
async function copyToClipboard() {
|
|
27
|
+
try {
|
|
28
|
+
await navigator.clipboard.writeText(code)
|
|
29
|
+
copied = true
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
copied = false
|
|
32
|
+
}, 2000)
|
|
33
|
+
} catch {
|
|
34
|
+
// Fallback for older browsers
|
|
35
|
+
const textarea = document.createElement('textarea')
|
|
36
|
+
textarea.value = code
|
|
37
|
+
textarea.style.position = 'fixed'
|
|
38
|
+
textarea.style.opacity = '0'
|
|
39
|
+
document.body.appendChild(textarea)
|
|
40
|
+
textarea.select()
|
|
41
|
+
document.execCommand('copy')
|
|
42
|
+
document.body.removeChild(textarea)
|
|
43
|
+
copied = true
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
copied = false
|
|
46
|
+
}, 2000)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<div
|
|
52
|
+
class="code-block {className}"
|
|
53
|
+
class:show-line-numbers={showLineNumbers}
|
|
54
|
+
data-language={language}
|
|
55
|
+
data-theme={theme}
|
|
56
|
+
>
|
|
57
|
+
{#if showCopyButton}
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
class="copy-button"
|
|
61
|
+
onclick={copyToClipboard}
|
|
62
|
+
aria-label={copied ? labels.copied : labels.copy}
|
|
63
|
+
>
|
|
64
|
+
{#if copied}
|
|
65
|
+
<span class="copy-icon {icons.copysuccess}"></span>
|
|
66
|
+
{:else}
|
|
67
|
+
<span class="copy-icon {icons.copy}"></span>
|
|
68
|
+
{/if}
|
|
69
|
+
</button>
|
|
70
|
+
{/if}
|
|
71
|
+
|
|
72
|
+
{#await highlightedCode}
|
|
73
|
+
<div class="code-loading">
|
|
74
|
+
<pre><code>{code}</code></pre>
|
|
75
|
+
</div>
|
|
76
|
+
{:then html}
|
|
77
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
78
|
+
{@html html}
|
|
79
|
+
{:catch error}
|
|
80
|
+
<div class="code-error">
|
|
81
|
+
<pre><code>{code}</code></pre>
|
|
82
|
+
<p class="error-message">Highlighting failed: {error.message}</p>
|
|
83
|
+
</div>
|
|
84
|
+
{/await}
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<style>
|
|
88
|
+
.code-block {
|
|
89
|
+
position: relative;
|
|
90
|
+
border-radius: 0.5rem;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.code-block :global(pre) {
|
|
95
|
+
margin: 0;
|
|
96
|
+
padding: 1rem;
|
|
97
|
+
overflow-x: auto;
|
|
98
|
+
font-family: 'Victor Mono', ui-monospace, monospace;
|
|
99
|
+
font-size: 0.875rem;
|
|
100
|
+
line-height: 1.6;
|
|
101
|
+
tab-size: 2;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.code-block :global(code) {
|
|
105
|
+
font-family: inherit;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Line numbers */
|
|
109
|
+
.code-block.show-line-numbers :global(pre) {
|
|
110
|
+
padding-left: 3.5rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.code-block.show-line-numbers :global(.line::before) {
|
|
114
|
+
content: counter(line);
|
|
115
|
+
counter-increment: line;
|
|
116
|
+
display: inline-block;
|
|
117
|
+
width: 2rem;
|
|
118
|
+
margin-left: -3rem;
|
|
119
|
+
margin-right: 1rem;
|
|
120
|
+
text-align: right;
|
|
121
|
+
color: var(--code-line-number-color, hsl(220 10% 40%));
|
|
122
|
+
user-select: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.code-block.show-line-numbers :global(pre) {
|
|
126
|
+
counter-reset: line;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Copy button */
|
|
130
|
+
.copy-button {
|
|
131
|
+
position: absolute;
|
|
132
|
+
top: 0.5rem;
|
|
133
|
+
right: 0.5rem;
|
|
134
|
+
padding: 0.375rem;
|
|
135
|
+
background: var(--code-copy-bg, hsla(220, 15%, 25%, 0.8));
|
|
136
|
+
border: 1px solid var(--code-copy-border, hsl(220 15% 35%));
|
|
137
|
+
border-radius: 0.375rem;
|
|
138
|
+
color: var(--code-copy-color, hsl(220 10% 70%));
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
opacity: 0;
|
|
141
|
+
transition:
|
|
142
|
+
opacity 0.15s ease,
|
|
143
|
+
background-color 0.15s ease;
|
|
144
|
+
z-index: 10;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.code-block:hover .copy-button,
|
|
148
|
+
.copy-button:focus {
|
|
149
|
+
opacity: 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.copy-button:hover {
|
|
153
|
+
background: var(--code-copy-bg-hover, hsla(220, 15%, 30%, 0.9));
|
|
154
|
+
color: var(--code-copy-color-hover, hsl(220 10% 90%));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.copy-icon {
|
|
158
|
+
display: block;
|
|
159
|
+
width: 1rem;
|
|
160
|
+
height: 1rem;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Loading state */
|
|
164
|
+
.code-loading {
|
|
165
|
+
background: var(--code-bg, hsl(220 15% 13%));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.code-loading pre {
|
|
169
|
+
color: var(--code-color, hsl(220 10% 60%));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Error state */
|
|
173
|
+
.code-error {
|
|
174
|
+
background: var(--code-bg, hsl(220 15% 13%));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.code-error pre {
|
|
178
|
+
color: var(--code-color, hsl(220 10% 70%));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.error-message {
|
|
182
|
+
padding: 0.5rem 1rem;
|
|
183
|
+
margin: 0;
|
|
184
|
+
font-size: 0.75rem;
|
|
185
|
+
color: var(--code-error-color, hsl(0 70% 60%));
|
|
186
|
+
background: var(--code-error-bg, hsla(0, 70%, 30%, 0.2));
|
|
187
|
+
border-top: 1px solid var(--code-error-border, hsl(0 70% 40%));
|
|
188
|
+
}
|
|
189
|
+
</style>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Connector Component
|
|
4
|
+
*
|
|
5
|
+
* Renders tree line connectors for hierarchical visualizations.
|
|
6
|
+
* Supports RTL (right-to-left) layouts.
|
|
7
|
+
*
|
|
8
|
+
* Based on @rokkit/ui Connector implementation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ConnectorType } from '../types/tree.js'
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
/** The type of connector to render */
|
|
15
|
+
type?: ConnectorType
|
|
16
|
+
/** Right-to-left layout support */
|
|
17
|
+
rtl?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { type = 'empty', rtl = false }: Props = $props()
|
|
21
|
+
|
|
22
|
+
const validTypes: ConnectorType[] = ['last', 'child', 'sibling', 'empty']
|
|
23
|
+
const validatedType = $derived(validTypes.includes(type) ? type : 'empty')
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<span data-connector data-connector-type={validatedType} data-connector-rtl={rtl || undefined}>
|
|
27
|
+
{#if validatedType === 'last'}
|
|
28
|
+
{#if rtl}
|
|
29
|
+
<i data-connector-v></i>
|
|
30
|
+
<i data-connector-corner></i>
|
|
31
|
+
{:else}
|
|
32
|
+
<i data-connector-v></i>
|
|
33
|
+
<i data-connector-h></i>
|
|
34
|
+
{/if}
|
|
35
|
+
{:else if validatedType === 'child'}
|
|
36
|
+
{#if rtl}
|
|
37
|
+
<i data-connector-v data-connector-full></i>
|
|
38
|
+
<i data-connector-corner></i>
|
|
39
|
+
{:else}
|
|
40
|
+
<i data-connector-v data-connector-full></i>
|
|
41
|
+
<i data-connector-h></i>
|
|
42
|
+
{/if}
|
|
43
|
+
{:else if validatedType === 'sibling'}
|
|
44
|
+
<i data-connector-v data-connector-full></i>
|
|
45
|
+
{/if}
|
|
46
|
+
</span>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface DividerProps {
|
|
3
|
+
/** Orientation of the divider */
|
|
4
|
+
orientation?: 'horizontal' | 'vertical'
|
|
5
|
+
/** Optional label text displayed in the center */
|
|
6
|
+
label?: string
|
|
7
|
+
/** Additional CSS class */
|
|
8
|
+
class?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { orientation = 'horizontal', label, class: className = '' }: DividerProps = $props()
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div
|
|
15
|
+
data-divider
|
|
16
|
+
data-orientation={orientation}
|
|
17
|
+
class={className || undefined}
|
|
18
|
+
role="separator"
|
|
19
|
+
aria-orientation={orientation}
|
|
20
|
+
>
|
|
21
|
+
{#if label}
|
|
22
|
+
<span data-divider-label>{label}</span>
|
|
23
|
+
{/if}
|
|
24
|
+
</div>
|