@nucel/ui 0.3.0 → 0.10.0
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/package.json +8 -36
- package/src/lib/components/BottomSheet.svelte +96 -0
- package/src/lib/components/Breadcrumbs.svelte +57 -0
- package/src/lib/components/Checkbox.svelte +64 -0
- package/src/lib/components/CodeBlock.svelte +264 -0
- package/src/lib/components/CodeEditor.svelte +175 -0
- package/src/lib/components/ColorInput.svelte +41 -0
- package/src/lib/components/ColorInput.test.ts +126 -0
- package/src/lib/components/Combobox.svelte +103 -0
- package/src/lib/components/CommandPalette.svelte +135 -0
- package/src/lib/components/CopyButton.svelte +95 -0
- package/src/lib/components/CopyButton.test.ts +213 -0
- package/src/lib/components/DataTable.svelte +202 -0
- package/src/lib/components/DateRangePicker.svelte +185 -0
- package/src/lib/components/DiffEditor.svelte +174 -0
- package/src/lib/components/Drawer.svelte +69 -0
- package/src/lib/components/Fab.svelte +59 -0
- package/src/lib/components/Form.svelte +38 -0
- package/src/lib/components/FormField.svelte +51 -0
- package/src/lib/components/IconButton.svelte +86 -0
- package/src/lib/components/IconButton.test.ts +139 -0
- package/src/lib/components/InlineCode.svelte +28 -0
- package/src/lib/components/Pagination.svelte +65 -0
- package/src/lib/components/Radio.svelte +60 -0
- package/src/lib/components/RadioGroup.svelte +26 -0
- package/src/lib/components/SearchInput.svelte +77 -0
- package/src/lib/components/Skeleton.svelte +76 -0
- package/src/lib/components/StatCard.svelte +97 -0
- package/src/lib/components/ThemeProvider.svelte +157 -0
- package/src/lib/components/ThemeToggle.svelte +68 -0
- package/src/lib/components/ThreeWayMerge.svelte +185 -0
- package/src/lib/components/ui/MarkdownRenderer.svelte +126 -8
- package/src/lib/components/ui/Sparkline.svelte +1 -1
- package/src/lib/components/ui/StatusBadge.svelte +6 -3
- package/src/lib/components/ui/StatusDot.svelte +3 -3
- package/src/lib/index.ts +113 -63
- package/src/lib/utils/cn.test.ts +993 -0
- package/src/lib/utils/detectLanguage.ts +187 -0
- package/src/lib/utils/monaco-workers.d.ts +32 -0
- package/src/lib/utils/monacoLoader.ts +167 -0
- package/src/lib/utils/shikiHighlighter.ts +78 -0
- package/src/styles.css +100 -32
- package/src/lib/components/ui/Alert.svelte +0 -47
- package/src/lib/components/ui/AppCard.svelte +0 -76
- package/src/lib/components/ui/AppShell.svelte +0 -14
- package/src/lib/components/ui/AppSidebar.svelte +0 -45
- package/src/lib/components/ui/BranchPill.svelte +0 -19
- package/src/lib/components/ui/CodeBlock.svelte +0 -92
- package/src/lib/components/ui/CommentPill.svelte +0 -12
- package/src/lib/components/ui/CopyButton.svelte +0 -43
- package/src/lib/components/ui/CostDisplay.svelte +0 -26
- package/src/lib/components/ui/FilterBar.svelte +0 -63
- package/src/lib/components/ui/FormField.svelte +0 -34
- package/src/lib/components/ui/KanbanBoard.svelte +0 -27
- package/src/lib/components/ui/KanbanCard.svelte +0 -43
- package/src/lib/components/ui/KanbanColumn.svelte +0 -52
- package/src/lib/components/ui/ListCard.svelte +0 -9
- package/src/lib/components/ui/MetricCard.svelte +0 -79
- package/src/lib/components/ui/NavItem.svelte +0 -42
- package/src/lib/components/ui/NavSection.svelte +0 -17
- package/src/lib/components/ui/PageHeader.svelte +0 -25
- package/src/lib/components/ui/Pagination.svelte +0 -85
- package/src/lib/components/ui/PermissionChips.svelte +0 -49
- package/src/lib/components/ui/Section.svelte +0 -21
- package/src/lib/components/ui/SectionTitle.svelte +0 -16
- package/src/lib/components/ui/StatCard.svelte +0 -19
- package/src/lib/components/ui/StatusPill.svelte +0 -54
- package/src/lib/components/ui/Timeline.svelte +0 -85
- package/src/lib/components/ui/editor/RichEditor.svelte +0 -580
- package/src/lib/components/ui/editor/mention-suggestion.ts +0 -144
- package/src/lib/components/ui/table/Table.svelte +0 -12
- package/src/lib/components/ui/table/TableBody.svelte +0 -10
- package/src/lib/components/ui/table/TableCaption.svelte +0 -10
- package/src/lib/components/ui/table/TableCell.svelte +0 -10
- package/src/lib/components/ui/table/TableHead.svelte +0 -10
- package/src/lib/components/ui/table/TableHeader.svelte +0 -10
- package/src/lib/components/ui/table/TableRow.svelte +0 -10
- package/src/lib/components/ui/table/index.ts +0 -7
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../utils.js';
|
|
3
|
+
import CodeEditor from './CodeEditor.svelte';
|
|
4
|
+
import type { CodeEditorTheme } from './CodeEditor.svelte';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
/** Common ancestor / merge base content. */
|
|
8
|
+
base?: string;
|
|
9
|
+
/** "Ours" side (current branch). */
|
|
10
|
+
ours?: string;
|
|
11
|
+
/** "Theirs" side (incoming branch). */
|
|
12
|
+
theirs?: string;
|
|
13
|
+
/** Resolved/merged content. Bindable — consumer reads this back. */
|
|
14
|
+
merged?: string;
|
|
15
|
+
/** Optional labels for each pane. */
|
|
16
|
+
baseLabel?: string;
|
|
17
|
+
oursLabel?: string;
|
|
18
|
+
theirsLabel?: string;
|
|
19
|
+
mergedLabel?: string;
|
|
20
|
+
/** Monaco language id. */
|
|
21
|
+
language?: string;
|
|
22
|
+
/** Theme mode. */
|
|
23
|
+
theme?: CodeEditorTheme;
|
|
24
|
+
/** Height per editor pane. */
|
|
25
|
+
paneHeight?: string;
|
|
26
|
+
/** Container className. */
|
|
27
|
+
class?: string;
|
|
28
|
+
/** Called when merged content changes. */
|
|
29
|
+
onchange?: (merged: string) => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
base = '',
|
|
34
|
+
ours = '',
|
|
35
|
+
theirs = '',
|
|
36
|
+
merged = $bindable(''),
|
|
37
|
+
baseLabel = 'Base',
|
|
38
|
+
oursLabel = 'Ours',
|
|
39
|
+
theirsLabel = 'Theirs',
|
|
40
|
+
mergedLabel = 'Merged result',
|
|
41
|
+
language = 'plaintext',
|
|
42
|
+
theme = 'auto',
|
|
43
|
+
paneHeight = '320px',
|
|
44
|
+
class: className,
|
|
45
|
+
onchange,
|
|
46
|
+
}: Props = $props();
|
|
47
|
+
|
|
48
|
+
function acceptOurs() {
|
|
49
|
+
merged = ours;
|
|
50
|
+
onchange?.(merged);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function acceptTheirs() {
|
|
54
|
+
merged = theirs;
|
|
55
|
+
onchange?.(merged);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function acceptBase() {
|
|
59
|
+
merged = base;
|
|
60
|
+
onchange?.(merged);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function acceptCombined() {
|
|
64
|
+
// Crude but useful starting point: concatenate ours then theirs with
|
|
65
|
+
// a marker between. Consumers wanting a smarter merge should run a
|
|
66
|
+
// proper 3-way text-merge on the backend and feed `merged` directly.
|
|
67
|
+
merged = `${ours}\n\n// ===== incoming =====\n${theirs}`;
|
|
68
|
+
onchange?.(merged);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleMergedChange(next: string) {
|
|
72
|
+
onchange?.(next);
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<div
|
|
77
|
+
data-slot="three-way-merge"
|
|
78
|
+
class={cn('flex w-full flex-col gap-3', className)}
|
|
79
|
+
role="region"
|
|
80
|
+
aria-label="Three-way merge"
|
|
81
|
+
>
|
|
82
|
+
<div class="grid grid-cols-1 gap-3 md:grid-cols-3">
|
|
83
|
+
<div class="flex flex-col gap-1.5">
|
|
84
|
+
<div class="text-muted-foreground flex items-center justify-between text-xs font-medium">
|
|
85
|
+
<span>{baseLabel}</span>
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
class="hover:text-foreground rounded px-1.5 py-0.5 text-[11px] outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
89
|
+
onclick={acceptBase}
|
|
90
|
+
>
|
|
91
|
+
Accept base
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
<CodeEditor
|
|
95
|
+
value={base}
|
|
96
|
+
{language}
|
|
97
|
+
{theme}
|
|
98
|
+
readOnly
|
|
99
|
+
height={paneHeight}
|
|
100
|
+
ariaLabel={`${baseLabel} (read-only)`}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div class="flex flex-col gap-1.5">
|
|
105
|
+
<div class="text-muted-foreground flex items-center justify-between text-xs font-medium">
|
|
106
|
+
<span class="text-success">{oursLabel}</span>
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
class="hover:text-foreground rounded px-1.5 py-0.5 text-[11px] outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
110
|
+
onclick={acceptOurs}
|
|
111
|
+
>
|
|
112
|
+
Accept ours
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
<CodeEditor
|
|
116
|
+
value={ours}
|
|
117
|
+
{language}
|
|
118
|
+
{theme}
|
|
119
|
+
readOnly
|
|
120
|
+
height={paneHeight}
|
|
121
|
+
ariaLabel={`${oursLabel} (read-only)`}
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div class="flex flex-col gap-1.5">
|
|
126
|
+
<div class="text-muted-foreground flex items-center justify-between text-xs font-medium">
|
|
127
|
+
<span class="text-warning">{theirsLabel}</span>
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
class="hover:text-foreground rounded px-1.5 py-0.5 text-[11px] outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
131
|
+
onclick={acceptTheirs}
|
|
132
|
+
>
|
|
133
|
+
Accept theirs
|
|
134
|
+
</button>
|
|
135
|
+
</div>
|
|
136
|
+
<CodeEditor
|
|
137
|
+
value={theirs}
|
|
138
|
+
{language}
|
|
139
|
+
{theme}
|
|
140
|
+
readOnly
|
|
141
|
+
height={paneHeight}
|
|
142
|
+
ariaLabel={`${theirsLabel} (read-only)`}
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<div class="flex flex-col gap-1.5">
|
|
148
|
+
<div class="flex items-center justify-between gap-2">
|
|
149
|
+
<span class="text-foreground text-sm font-medium">{mergedLabel}</span>
|
|
150
|
+
<div class="flex items-center gap-2">
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
class="hover:bg-accent rounded-md border border-border px-2 py-1 text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
154
|
+
onclick={acceptCombined}
|
|
155
|
+
title="Concatenate ours + theirs as a starting point for manual edit"
|
|
156
|
+
>
|
|
157
|
+
Combine
|
|
158
|
+
</button>
|
|
159
|
+
<button
|
|
160
|
+
type="button"
|
|
161
|
+
class="hover:bg-accent rounded-md border border-border px-2 py-1 text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
162
|
+
onclick={acceptOurs}
|
|
163
|
+
>
|
|
164
|
+
Use ours
|
|
165
|
+
</button>
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
class="hover:bg-accent rounded-md border border-border px-2 py-1 text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
|
|
169
|
+
onclick={acceptTheirs}
|
|
170
|
+
>
|
|
171
|
+
Use theirs
|
|
172
|
+
</button>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
<CodeEditor
|
|
176
|
+
bind:value={merged}
|
|
177
|
+
{language}
|
|
178
|
+
{theme}
|
|
179
|
+
height={paneHeight}
|
|
180
|
+
placeholder="Edit the merged result here…"
|
|
181
|
+
ariaLabel={mergedLabel}
|
|
182
|
+
onchange={handleMergedChange}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
@@ -1,16 +1,113 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { marked } from 'marked';
|
|
3
3
|
import DOMPurify from 'dompurify';
|
|
4
|
+
import { onMount, tick } from 'svelte';
|
|
5
|
+
import {
|
|
6
|
+
getHighlighter,
|
|
7
|
+
loadLanguage,
|
|
8
|
+
resolveLang,
|
|
9
|
+
SHIKI_LIGHT_THEME,
|
|
10
|
+
SHIKI_DARK_THEME,
|
|
11
|
+
} from '../../utils/shikiHighlighter.js';
|
|
4
12
|
|
|
5
|
-
let {
|
|
13
|
+
let {
|
|
14
|
+
content,
|
|
15
|
+
highlight = true,
|
|
16
|
+
}: {
|
|
17
|
+
content: string;
|
|
18
|
+
/**
|
|
19
|
+
* When true (default), fenced code blocks are run through Shiki after
|
|
20
|
+
* the markdown is rendered. Set to false to fall back to plain `<pre>`.
|
|
21
|
+
*/
|
|
22
|
+
highlight?: boolean;
|
|
23
|
+
} = $props();
|
|
24
|
+
|
|
25
|
+
let host = $state<HTMLDivElement | undefined>(undefined);
|
|
26
|
+
let isDark = $state(false);
|
|
6
27
|
|
|
7
28
|
let html = $derived(
|
|
8
|
-
|
|
29
|
+
DOMPurify.sanitize(marked.parse(content, { gfm: true, breaks: false }) as string, {
|
|
30
|
+
// Allow Shiki's inline style attributes on spans (colour tokens).
|
|
31
|
+
ADD_ATTR: ['style', 'data-line', 'data-language'],
|
|
32
|
+
}),
|
|
9
33
|
);
|
|
34
|
+
|
|
35
|
+
function readDarkMode(): boolean {
|
|
36
|
+
if (typeof document === 'undefined') return false;
|
|
37
|
+
return document.documentElement.classList.contains('dark');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
onMount(() => {
|
|
41
|
+
isDark = readDarkMode();
|
|
42
|
+
const mo = new MutationObserver(() => {
|
|
43
|
+
isDark = readDarkMode();
|
|
44
|
+
});
|
|
45
|
+
mo.observe(document.documentElement, {
|
|
46
|
+
attributes: true,
|
|
47
|
+
attributeFilter: ['class'],
|
|
48
|
+
});
|
|
49
|
+
return () => mo.disconnect();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Re-highlight whenever the markdown re-renders or the theme flips.
|
|
53
|
+
$effect(() => {
|
|
54
|
+
void html;
|
|
55
|
+
void isDark;
|
|
56
|
+
if (!highlight || typeof window === 'undefined' || !host) return;
|
|
57
|
+
|
|
58
|
+
let cancelled = false;
|
|
59
|
+
(async () => {
|
|
60
|
+
// Wait one tick so the @html DOM is in place.
|
|
61
|
+
await tick();
|
|
62
|
+
if (cancelled || !host) return;
|
|
63
|
+
|
|
64
|
+
const blocks = host.querySelectorAll('pre > code[class*="language-"]');
|
|
65
|
+
if (blocks.length === 0) return;
|
|
66
|
+
|
|
67
|
+
const theme = isDark ? SHIKI_DARK_THEME : SHIKI_LIGHT_THEME;
|
|
68
|
+
const hl = await getHighlighter();
|
|
69
|
+
|
|
70
|
+
for (const block of Array.from(blocks)) {
|
|
71
|
+
if (cancelled) return;
|
|
72
|
+
const pre = block.parentElement;
|
|
73
|
+
if (!pre || pre.dataset.shikiDone === 'true') continue;
|
|
74
|
+
|
|
75
|
+
const langClass = Array.from(block.classList).find((c) =>
|
|
76
|
+
c.startsWith('language-'),
|
|
77
|
+
);
|
|
78
|
+
const requested = langClass ? langClass.slice('language-'.length) : 'plaintext';
|
|
79
|
+
|
|
80
|
+
await loadLanguage(requested);
|
|
81
|
+
const lang = resolveLang(requested);
|
|
82
|
+
const code = block.textContent ?? '';
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const shikiHtml = hl.codeToHtml(code, { lang, theme });
|
|
86
|
+
// Shiki only emits <pre><code><span> with style="color: #xxx"
|
|
87
|
+
// attributes — fully trusted output, but parse it via the DOM
|
|
88
|
+
// parser (rather than innerHTML) to keep our defensive defaults.
|
|
89
|
+
const doc = new DOMParser().parseFromString(shikiHtml, 'text/html');
|
|
90
|
+
const next = doc.body.firstElementChild;
|
|
91
|
+
if (next) {
|
|
92
|
+
(next as HTMLElement).dataset.shikiDone = 'true';
|
|
93
|
+
(next as HTMLElement).dataset.lang = requested;
|
|
94
|
+
pre.replaceWith(next);
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.warn('[@nucel/ui MarkdownRenderer] Shiki failed:', err);
|
|
98
|
+
pre.dataset.shikiDone = 'true';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
})();
|
|
102
|
+
|
|
103
|
+
return () => {
|
|
104
|
+
cancelled = true;
|
|
105
|
+
};
|
|
106
|
+
});
|
|
10
107
|
</script>
|
|
11
108
|
|
|
12
|
-
|
|
13
|
-
|
|
109
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
110
|
+
<div bind:this={host} class="md-body" data-theme={isDark ? 'dark' : 'light'}>
|
|
14
111
|
{@html html}
|
|
15
112
|
</div>
|
|
16
113
|
|
|
@@ -27,7 +124,8 @@
|
|
|
27
124
|
padding-bottom: 0.5rem;
|
|
28
125
|
color: var(--foreground);
|
|
29
126
|
}
|
|
30
|
-
|
|
127
|
+
/* Inline code (not inside a <pre>) — keep simple. */
|
|
128
|
+
.md-body :global(code:not(pre code)) {
|
|
31
129
|
background: var(--muted);
|
|
32
130
|
padding: 0.125rem 0.375rem;
|
|
33
131
|
border-radius: 0.25rem;
|
|
@@ -35,17 +133,37 @@
|
|
|
35
133
|
font-size: 0.875em;
|
|
36
134
|
color: var(--foreground);
|
|
37
135
|
}
|
|
136
|
+
/* Fallback <pre> (before Shiki replaces it, or when highlight=false). */
|
|
38
137
|
.md-body :global(pre) {
|
|
39
|
-
background: var(--
|
|
138
|
+
background: var(--muted);
|
|
40
139
|
padding: 1rem;
|
|
41
|
-
border-radius: 0.
|
|
140
|
+
border-radius: 0.5rem;
|
|
42
141
|
overflow-x: auto;
|
|
43
142
|
border: 1px solid var(--border);
|
|
44
143
|
}
|
|
45
144
|
.md-body :global(pre code) {
|
|
46
145
|
background: none;
|
|
47
146
|
padding: 0;
|
|
48
|
-
color: var(--
|
|
147
|
+
color: var(--foreground);
|
|
148
|
+
}
|
|
149
|
+
/* Shiki-rendered <pre class="shiki ...">.
|
|
150
|
+
The colour tokens come from Shiki; we own the chrome. */
|
|
151
|
+
.md-body :global(pre.shiki) {
|
|
152
|
+
margin: 1rem 0;
|
|
153
|
+
padding: 1rem;
|
|
154
|
+
border-radius: 0.5rem;
|
|
155
|
+
border: 1px solid var(--border);
|
|
156
|
+
overflow-x: auto;
|
|
157
|
+
font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
|
|
158
|
+
font-size: 0.85rem;
|
|
159
|
+
line-height: 1.55;
|
|
160
|
+
tab-size: 2;
|
|
161
|
+
}
|
|
162
|
+
.md-body :global(pre.shiki code) {
|
|
163
|
+
display: block;
|
|
164
|
+
min-width: max-content;
|
|
165
|
+
background: transparent;
|
|
166
|
+
padding: 0;
|
|
49
167
|
}
|
|
50
168
|
.md-body :global(a) {
|
|
51
169
|
color: var(--primary);
|
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
const classes: Record<string, string> = {
|
|
10
10
|
open: 'border-success/30 bg-success/10 text-success',
|
|
11
11
|
closed: '',
|
|
12
|
-
merged:
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
merged:
|
|
13
|
+
'border-purple-500/30 bg-purple-500/10 text-purple-700 dark:border-purple-400/30 dark:bg-purple-400/10 dark:text-purple-300',
|
|
14
|
+
running:
|
|
15
|
+
'border-blue-500/30 bg-blue-500/10 text-blue-700 dark:border-blue-400/30 dark:bg-blue-400/10 dark:text-blue-300',
|
|
16
|
+
active:
|
|
17
|
+
'border-blue-500/30 bg-blue-500/10 text-blue-700 dark:border-blue-400/30 dark:bg-blue-400/10 dark:text-blue-300',
|
|
15
18
|
passed: 'border-success/30 bg-success/10 text-success',
|
|
16
19
|
succeeded: 'border-success/30 bg-success/10 text-success',
|
|
17
20
|
failed: 'border-destructive/30 bg-destructive/10 text-destructive',
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
closed: 'bg-muted-foreground',
|
|
42
42
|
paused: 'bg-muted-foreground',
|
|
43
43
|
terminated: 'bg-muted-foreground',
|
|
44
|
-
running: 'bg-blue-500',
|
|
45
|
-
merged: 'bg-purple-500',
|
|
44
|
+
running: 'bg-blue-500 dark:bg-blue-400',
|
|
45
|
+
merged: 'bg-purple-500 dark:bg-purple-400',
|
|
46
46
|
failed: 'bg-destructive',
|
|
47
47
|
error: 'bg-destructive',
|
|
48
|
-
warning: 'bg-
|
|
48
|
+
warning: 'bg-warning',
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
const resolvedColor = $derived(
|
package/src/lib/index.ts
CHANGED
|
@@ -163,8 +163,8 @@ export {
|
|
|
163
163
|
// Separator
|
|
164
164
|
export { Separator } from './components/ui/separator/index.js';
|
|
165
165
|
|
|
166
|
-
// Skeleton
|
|
167
|
-
export { Skeleton } from './components/ui/skeleton/index.js';
|
|
166
|
+
// Skeleton (primitive — kept for backwards-compat)
|
|
167
|
+
export { Skeleton as SkeletonPrimitive } from './components/ui/skeleton/index.js';
|
|
168
168
|
|
|
169
169
|
// Sonner (Toaster)
|
|
170
170
|
export { Toaster } from './components/ui/sonner/index.js';
|
|
@@ -243,86 +243,136 @@ export { default as Sparkline } from './components/ui/Sparkline.svelte';
|
|
|
243
243
|
// ProviderIcon
|
|
244
244
|
export { default as ProviderIcon } from './components/ui/ProviderIcon.svelte';
|
|
245
245
|
|
|
246
|
-
//
|
|
247
|
-
export { default as
|
|
246
|
+
// Checkbox (shadcn-styled wrapper over bits-ui)
|
|
247
|
+
export { default as Checkbox } from './components/Checkbox.svelte';
|
|
248
248
|
|
|
249
|
-
//
|
|
250
|
-
export { default as
|
|
249
|
+
// RadioGroup + Radio (shadcn-styled wrappers over bits-ui)
|
|
250
|
+
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
251
|
+
export { default as Radio } from './components/Radio.svelte';
|
|
251
252
|
|
|
252
|
-
//
|
|
253
|
-
export { default as
|
|
253
|
+
// Form + FormField (InertiaJS-friendly defaults)
|
|
254
|
+
export { default as Form } from './components/Form.svelte';
|
|
255
|
+
export { default as FormField } from './components/FormField.svelte';
|
|
254
256
|
|
|
255
|
-
//
|
|
256
|
-
export { default as
|
|
257
|
+
// Pagination (composite, bits-ui driven)
|
|
258
|
+
export { default as Pagination } from './components/Pagination.svelte';
|
|
257
259
|
|
|
258
|
-
//
|
|
259
|
-
export {
|
|
260
|
-
Table,
|
|
261
|
-
TableHeader,
|
|
262
|
-
TableBody,
|
|
263
|
-
TableRow,
|
|
264
|
-
TableHead,
|
|
265
|
-
TableCell,
|
|
266
|
-
TableCaption,
|
|
267
|
-
} from './components/ui/table/index.js';
|
|
260
|
+
// StatCard (dashboard metric)
|
|
261
|
+
export { default as StatCard } from './components/StatCard.svelte';
|
|
268
262
|
|
|
269
|
-
//
|
|
270
|
-
export { default as
|
|
263
|
+
// Breadcrumbs (array-driven composite; for low-level pieces use Breadcrumb*)
|
|
264
|
+
export { default as Breadcrumbs } from './components/Breadcrumbs.svelte';
|
|
271
265
|
|
|
272
|
-
//
|
|
273
|
-
export { default as ListCard } from './components/ui/ListCard.svelte';
|
|
266
|
+
// ---- 0.5.0 additions ----
|
|
274
267
|
|
|
275
|
-
//
|
|
276
|
-
export { default as
|
|
268
|
+
// DataTable
|
|
269
|
+
export { default as DataTable } from './components/DataTable.svelte';
|
|
270
|
+
export type { ColumnDef, SortDirection } from './components/DataTable.svelte';
|
|
277
271
|
|
|
278
|
-
//
|
|
279
|
-
export { default as
|
|
272
|
+
// SearchInput
|
|
273
|
+
export { default as SearchInput } from './components/SearchInput.svelte';
|
|
280
274
|
|
|
281
|
-
//
|
|
282
|
-
export { default as
|
|
275
|
+
// DateRangePicker
|
|
276
|
+
export { default as DateRangePicker } from './components/DateRangePicker.svelte';
|
|
277
|
+
export type { DateRange, DateRangePreset } from './components/DateRangePicker.svelte';
|
|
278
|
+
export { DEFAULT_PRESETS as DateRangePickerDefaultPresets } from './components/DateRangePicker.svelte';
|
|
283
279
|
|
|
284
|
-
//
|
|
285
|
-
export { default as
|
|
280
|
+
// CodeBlock
|
|
281
|
+
export { default as CodeBlock } from './components/CodeBlock.svelte';
|
|
286
282
|
|
|
287
|
-
//
|
|
288
|
-
export { default as
|
|
283
|
+
// Skeleton (composite — shimmer + width/height; supersedes SkeletonPrimitive)
|
|
284
|
+
export { default as Skeleton } from './components/Skeleton.svelte';
|
|
289
285
|
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
// `dndzone` to whichever container they want sortable.
|
|
293
|
-
export { default as KanbanBoard } from './components/ui/KanbanBoard.svelte';
|
|
294
|
-
export { default as KanbanColumn } from './components/ui/KanbanColumn.svelte';
|
|
295
|
-
export { default as KanbanCard } from './components/ui/KanbanCard.svelte';
|
|
286
|
+
// Drawer (side panel, built on Sheet primitive)
|
|
287
|
+
export { default as Drawer } from './components/Drawer.svelte';
|
|
296
288
|
|
|
297
|
-
//
|
|
298
|
-
export { default as
|
|
289
|
+
// Combobox (searchable Select built on bits-ui Combobox)
|
|
290
|
+
export { default as Combobox } from './components/Combobox.svelte';
|
|
291
|
+
export type { ComboboxOption } from './components/Combobox.svelte';
|
|
299
292
|
|
|
300
|
-
//
|
|
301
|
-
export { default as
|
|
293
|
+
// CommandPalette (cmdk-style palette built on bits-ui Command + Dialog)
|
|
294
|
+
export { default as CommandPalette } from './components/CommandPalette.svelte';
|
|
295
|
+
export type { CommandPaletteItem } from './components/CommandPalette.svelte';
|
|
302
296
|
|
|
303
|
-
//
|
|
304
|
-
export { default as FormField } from './components/ui/FormField.svelte';
|
|
297
|
+
// ---- 0.6.0 additions ----
|
|
305
298
|
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
export { default as
|
|
310
|
-
export {
|
|
299
|
+
// ThemeProvider — exposes a `theme` ('light' | 'dark' | 'system') context,
|
|
300
|
+
// persists the user's preference in localStorage, and reacts to
|
|
301
|
+
// `prefers-color-scheme` when set to 'system'.
|
|
302
|
+
export { default as ThemeProvider } from './components/ThemeProvider.svelte';
|
|
303
|
+
export {
|
|
304
|
+
getThemeContext,
|
|
305
|
+
type Theme,
|
|
306
|
+
type ResolvedTheme,
|
|
307
|
+
type ThemeContext,
|
|
308
|
+
} from './components/ThemeProvider.svelte';
|
|
311
309
|
|
|
312
|
-
//
|
|
313
|
-
export { default as
|
|
310
|
+
// ThemeToggle — icon button cycling system -> light -> dark -> system.
|
|
311
|
+
export { default as ThemeToggle } from './components/ThemeToggle.svelte';
|
|
314
312
|
|
|
315
|
-
//
|
|
316
|
-
export { default as Pagination } from './components/ui/Pagination.svelte';
|
|
313
|
+
// ---- 0.7.0 additions ----
|
|
317
314
|
|
|
318
|
-
//
|
|
319
|
-
|
|
315
|
+
// CodeEditor — Monaco-powered editable code editor.
|
|
316
|
+
// Lazy-loaded on first mount; SSR-safe; matches @nucel/ui design tokens.
|
|
317
|
+
export { default as CodeEditor } from './components/CodeEditor.svelte';
|
|
318
|
+
export type { CodeEditorTheme } from './components/CodeEditor.svelte';
|
|
319
|
+
|
|
320
|
+
// DiffEditor — Monaco-powered side-by-side or inline diff view.
|
|
321
|
+
export { default as DiffEditor } from './components/DiffEditor.svelte';
|
|
320
322
|
|
|
321
|
-
//
|
|
322
|
-
export { default as
|
|
323
|
+
// ThreeWayMerge — base/ours/theirs/merged conflict-resolution UI on top of Monaco.
|
|
324
|
+
export { default as ThreeWayMerge } from './components/ThreeWayMerge.svelte';
|
|
323
325
|
|
|
324
|
-
//
|
|
325
|
-
export {
|
|
326
|
+
// Monaco loader (advanced — use the components above when possible).
|
|
327
|
+
export { loadMonaco, resolveMonacoTheme } from './utils/monacoLoader.js';
|
|
326
328
|
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
+
// ---- 0.8.0 additions ----
|
|
330
|
+
|
|
331
|
+
// InlineCode — single-line <code> with subtle background, no highlighting.
|
|
332
|
+
export { default as InlineCode } from './components/InlineCode.svelte';
|
|
333
|
+
|
|
334
|
+
// Shiki helpers (re-exported so consumers can warm the highlighter early or
|
|
335
|
+
// load extra languages outside CodeBlock — e.g. for a route prefetch hook).
|
|
336
|
+
export {
|
|
337
|
+
getHighlighter as getShikiHighlighter,
|
|
338
|
+
loadLanguage as loadShikiLanguage,
|
|
339
|
+
SHIKI_LIGHT_THEME,
|
|
340
|
+
SHIKI_DARK_THEME,
|
|
341
|
+
} from './utils/shikiHighlighter.js';
|
|
342
|
+
|
|
343
|
+
// File-path → Shiki language id helper, useful for repo file viewers.
|
|
344
|
+
export { detectLanguageFromPath } from './utils/detectLanguage.js';
|
|
345
|
+
|
|
346
|
+
// ---- 0.9.0 additions (mobile primitives) ----
|
|
347
|
+
|
|
348
|
+
// BottomSheet — mobile bottom-anchored sheet (Sheet primitive with grabber +
|
|
349
|
+
// safe-area). Pair with `md:hidden` on triggers to keep desktop unchanged.
|
|
350
|
+
export { default as BottomSheet } from './components/BottomSheet.svelte';
|
|
351
|
+
|
|
352
|
+
// Fab — floating action button for mobile primary actions.
|
|
353
|
+
// Defaults to `md:hidden`; pass `alwaysVisible` to show on desktop too.
|
|
354
|
+
export { default as Fab } from './components/Fab.svelte';
|
|
355
|
+
|
|
356
|
+
// ---- 0.10.0 additions (raw-primitive gap closers) ----
|
|
357
|
+
|
|
358
|
+
// CopyButton — copy-to-clipboard button with built-in "Copied" feedback and
|
|
359
|
+
// timeout reset. Replaces the duplicated clipboard.writeText + copied-state
|
|
360
|
+
// pattern in repo clone boxes, token reveal screens, session viewers, etc.
|
|
361
|
+
export { default as CopyButton } from './components/CopyButton.svelte';
|
|
362
|
+
export type { CopyButtonVariant, CopyButtonSize } from './components/CopyButton.svelte';
|
|
363
|
+
|
|
364
|
+
// IconButton — icon-only button OR anchor (pass `href`). Defaults to the
|
|
365
|
+
// muted-foreground "toolbar glyph" look (hover:bg-accent) used in TopBar,
|
|
366
|
+
// file trees, and dismiss buttons. `size="tap"` gives a 44×44 mobile target.
|
|
367
|
+
// `aria-label` is required.
|
|
368
|
+
export { default as IconButton } from './components/IconButton.svelte';
|
|
369
|
+
export {
|
|
370
|
+
iconButtonVariants,
|
|
371
|
+
type IconButtonVariant,
|
|
372
|
+
type IconButtonSize,
|
|
373
|
+
type IconButtonProps,
|
|
374
|
+
} from './components/IconButton.svelte';
|
|
375
|
+
|
|
376
|
+
// ColorInput — styled native <input type="color"> wrapper matching the
|
|
377
|
+
// form-control border/ring/focus tokens. Optional `showValue` hex readout.
|
|
378
|
+
export { default as ColorInput } from './components/ColorInput.svelte';
|