@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.
Files changed (81) hide show
  1. package/README.md +159 -1
  2. package/package.json +52 -53
  3. package/src/MarkdownRenderer.svelte +106 -0
  4. package/src/components/AlertList.svelte +59 -0
  5. package/src/components/Avatar.svelte +68 -0
  6. package/src/components/Badge.svelte +57 -0
  7. package/src/components/BreadCrumbs.svelte +94 -0
  8. package/src/components/Button.svelte +91 -0
  9. package/src/components/ButtonGroup.svelte +9 -0
  10. package/src/components/Card.svelte +71 -0
  11. package/src/components/Carousel.svelte +169 -0
  12. package/src/components/Code.svelte +189 -0
  13. package/src/components/Connector.svelte +46 -0
  14. package/src/components/Divider.svelte +24 -0
  15. package/src/components/Dropdown.svelte +202 -0
  16. package/src/components/FloatingAction.svelte +367 -0
  17. package/src/components/FloatingNavigation.svelte +256 -0
  18. package/src/components/Grid.svelte +128 -0
  19. package/src/components/ItemContent.svelte +25 -0
  20. package/src/components/LazyTree.svelte +171 -0
  21. package/src/components/List.svelte +224 -0
  22. package/src/components/Menu.svelte +270 -0
  23. package/src/components/Message.svelte +75 -0
  24. package/src/components/MultiSelect.svelte +367 -0
  25. package/src/components/PaletteManager.svelte +360 -0
  26. package/src/components/Pill.svelte +83 -0
  27. package/src/components/ProgressBar.svelte +31 -0
  28. package/src/components/Range.svelte +346 -0
  29. package/src/components/Rating.svelte +115 -0
  30. package/src/components/Reveal.svelte +46 -0
  31. package/src/components/SearchFilter.svelte +88 -0
  32. package/src/components/Select.svelte +424 -0
  33. package/src/components/Shine.svelte +87 -0
  34. package/src/components/Stack.svelte +38 -0
  35. package/src/components/StatusList.svelte +18 -0
  36. package/src/components/Stepper.svelte +174 -0
  37. package/src/components/Swatch.svelte +102 -0
  38. package/src/components/Switch.svelte +71 -0
  39. package/src/components/Table.svelte +245 -0
  40. package/src/components/Tabs.svelte +202 -0
  41. package/src/components/Tilt.svelte +68 -0
  42. package/src/components/Timeline.svelte +61 -0
  43. package/src/components/Toggle.svelte +93 -0
  44. package/src/components/Toolbar.svelte +306 -0
  45. package/src/components/ToolbarGroup.svelte +17 -0
  46. package/src/components/Tooltip.svelte +121 -0
  47. package/src/components/Tree.svelte +143 -0
  48. package/src/components/UploadFileStatus.svelte +102 -0
  49. package/src/components/UploadProgress.svelte +127 -0
  50. package/src/components/UploadTarget.svelte +126 -0
  51. package/src/components/index.ts +48 -0
  52. package/src/index.ts +59 -0
  53. package/src/markdown-plugin.ts +12 -0
  54. package/src/types/button.ts +86 -0
  55. package/src/types/code.ts +46 -0
  56. package/src/types/floating-action.ts +123 -0
  57. package/src/types/floating-navigation.ts +80 -0
  58. package/src/types/index.ts +58 -0
  59. package/src/types/list.ts +200 -0
  60. package/src/types/menu.ts +95 -0
  61. package/src/types/palette.ts +160 -0
  62. package/src/types/range.ts +51 -0
  63. package/src/types/search-filter.ts +67 -0
  64. package/src/types/select.ts +176 -0
  65. package/src/types/switch.ts +68 -0
  66. package/src/types/table.ts +219 -0
  67. package/src/types/tabs.ts +103 -0
  68. package/src/types/timeline.ts +53 -0
  69. package/src/types/toggle.ts +68 -0
  70. package/src/types/toolbar.ts +167 -0
  71. package/src/types/tooltip.ts +21 -0
  72. package/src/types/tree.ts +250 -0
  73. package/src/types/upload-file-status.ts +45 -0
  74. package/src/types/upload-progress.ts +111 -0
  75. package/src/types/upload-target.ts +71 -0
  76. package/src/utils/palette.ts +618 -0
  77. package/src/utils/shiki.ts +114 -0
  78. package/src/utils/upload.js +145 -0
  79. package/LICENSE +0 -21
  80. package/src/index.js +0 -8
  81. 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>