@nucel/ui 0.1.0 → 0.3.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 +35 -3
- package/src/lib/components/ui/Alert.svelte +47 -0
- package/src/lib/components/ui/AppCard.svelte +76 -0
- package/src/lib/components/ui/AppShell.svelte +14 -0
- package/src/lib/components/ui/AppSidebar.svelte +45 -0
- package/src/lib/components/ui/BranchPill.svelte +19 -0
- package/src/lib/components/ui/CodeBlock.svelte +92 -0
- package/src/lib/components/ui/CommentPill.svelte +12 -0
- package/src/lib/components/ui/CopyButton.svelte +43 -0
- package/src/lib/components/ui/CostDisplay.svelte +26 -0
- package/src/lib/components/ui/FilterBar.svelte +63 -0
- package/src/lib/components/ui/FormField.svelte +34 -0
- package/src/lib/components/ui/KanbanBoard.svelte +27 -0
- package/src/lib/components/ui/KanbanCard.svelte +43 -0
- package/src/lib/components/ui/KanbanColumn.svelte +52 -0
- package/src/lib/components/ui/ListCard.svelte +9 -0
- package/src/lib/components/ui/MarkdownRenderer.svelte +2 -2
- package/src/lib/components/ui/MetricCard.svelte +79 -0
- package/src/lib/components/ui/NavItem.svelte +42 -0
- package/src/lib/components/ui/NavSection.svelte +17 -0
- package/src/lib/components/ui/PageHeader.svelte +25 -0
- package/src/lib/components/ui/Pagination.svelte +85 -0
- package/src/lib/components/ui/PermissionChips.svelte +49 -0
- package/src/lib/components/ui/Section.svelte +21 -0
- package/src/lib/components/ui/SectionTitle.svelte +16 -0
- package/src/lib/components/ui/Sparkline.svelte +1 -1
- package/src/lib/components/ui/StatCard.svelte +19 -0
- package/src/lib/components/ui/StatusPill.svelte +54 -0
- package/src/lib/components/ui/Timeline.svelte +85 -0
- package/src/lib/components/ui/editor/RichEditor.svelte +580 -0
- package/src/lib/components/ui/editor/mention-suggestion.ts +144 -0
- package/src/lib/components/ui/table/Table.svelte +12 -0
- package/src/lib/components/ui/table/TableBody.svelte +10 -0
- package/src/lib/components/ui/table/TableCaption.svelte +10 -0
- package/src/lib/components/ui/table/TableCell.svelte +10 -0
- package/src/lib/components/ui/table/TableHead.svelte +10 -0
- package/src/lib/components/ui/table/TableHeader.svelte +10 -0
- package/src/lib/components/ui/table/TableRow.svelte +10 -0
- package/src/lib/components/ui/table/index.ts +7 -0
- package/src/lib/index.ts +84 -0
- package/src/styles.css +6 -0
- package/src/lib/utils/cn.test.ts +0 -993
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import tippy, { type Instance, type Props as TippyProps } from 'tippy.js';
|
|
2
|
+
|
|
3
|
+
export type MentionItem = {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
type?: 'user' | 'team';
|
|
7
|
+
slug?: string;
|
|
8
|
+
avatar_url?: string;
|
|
9
|
+
sublabel?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function escapeHtml(text: string): string {
|
|
13
|
+
const div = document.createElement('div');
|
|
14
|
+
div.textContent = text;
|
|
15
|
+
return div.innerHTML;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getInitials(name: string): string {
|
|
19
|
+
return name.split(' ').map(p => p.charAt(0)).join('').toUpperCase().slice(0, 2);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createDropdown(props: { items: MentionItem[]; command: (item: MentionItem) => void }) {
|
|
23
|
+
const el = document.createElement('div');
|
|
24
|
+
el.className = 'mention-list';
|
|
25
|
+
let selectedIndex = 0;
|
|
26
|
+
let items = props.items;
|
|
27
|
+
let command = props.command;
|
|
28
|
+
|
|
29
|
+
function render() {
|
|
30
|
+
if (items.length === 0) {
|
|
31
|
+
el.innerHTML = `<div class="mention-list-empty">No results</div>`;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (items.length === 1 && (items[0] as any).type === 'hint') {
|
|
35
|
+
el.innerHTML = `<div class="mention-list-empty">${escapeHtml(items[0].label)}</div>`;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
el.innerHTML = items.map((item, i) => `
|
|
39
|
+
<button type="button" class="mention-list-item ${i === selectedIndex ? 'is-selected' : ''}" data-index="${i}">
|
|
40
|
+
${item.avatar_url
|
|
41
|
+
? `<img src="${escapeHtml(item.avatar_url)}" alt="" class="mention-avatar" />`
|
|
42
|
+
: `<span class="mention-avatar-fallback">${getInitials(item.label)}</span>`
|
|
43
|
+
}
|
|
44
|
+
<span class="mention-info">
|
|
45
|
+
<span class="mention-label">${escapeHtml(item.label)}</span>
|
|
46
|
+
${item.sublabel ? `<span class="mention-sublabel">${escapeHtml(item.sublabel)}</span>` : ''}
|
|
47
|
+
</span>
|
|
48
|
+
${item.type === 'team' ? `<span class="mention-badge">Team</span>` : ''}
|
|
49
|
+
</button>
|
|
50
|
+
`).join('');
|
|
51
|
+
|
|
52
|
+
el.querySelectorAll('.mention-list-item').forEach(btn => {
|
|
53
|
+
btn.addEventListener('click', () => {
|
|
54
|
+
const idx = parseInt((btn as HTMLElement).dataset.index ?? '0');
|
|
55
|
+
select(idx);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function select(idx: number) {
|
|
61
|
+
const item = items[idx];
|
|
62
|
+
if (item) command(item);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function updateSelection(idx: number) {
|
|
66
|
+
selectedIndex = ((idx % items.length) + items.length) % items.length;
|
|
67
|
+
render();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
render();
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
el,
|
|
74
|
+
update(newItems: MentionItem[], newCommand: (item: MentionItem) => void) {
|
|
75
|
+
items = newItems;
|
|
76
|
+
command = newCommand;
|
|
77
|
+
selectedIndex = 0;
|
|
78
|
+
render();
|
|
79
|
+
},
|
|
80
|
+
onKeyDown(key: string): boolean {
|
|
81
|
+
if (key === 'ArrowUp') { updateSelection(selectedIndex - 1); return true; }
|
|
82
|
+
if (key === 'ArrowDown') { updateSelection(selectedIndex + 1); return true; }
|
|
83
|
+
if (key === 'Enter') { select(selectedIndex); return true; }
|
|
84
|
+
return false;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createMentionSuggestion(mentionsUrl: string) {
|
|
90
|
+
return {
|
|
91
|
+
char: '@',
|
|
92
|
+
allowSpaces: false,
|
|
93
|
+
items: async ({ query }: { query: string }): Promise<MentionItem[]> => {
|
|
94
|
+
if (!query || query.length < 1) {
|
|
95
|
+
return [{ id: 'hint', label: 'Type to search users…', type: 'user' } as any];
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const sep = mentionsUrl.includes('?') ? '&' : '?';
|
|
99
|
+
const res = await fetch(`${mentionsUrl}${sep}query=${encodeURIComponent(query)}`, {
|
|
100
|
+
headers: { Accept: 'application/json' },
|
|
101
|
+
});
|
|
102
|
+
if (!res.ok) return [];
|
|
103
|
+
const data = await res.json();
|
|
104
|
+
return Array.isArray(data.suggestions) ? data.suggestions : [];
|
|
105
|
+
} catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
render: () => {
|
|
110
|
+
let dropdown: ReturnType<typeof createDropdown>;
|
|
111
|
+
let popup: Instance<TippyProps>[];
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
onStart(props: any) {
|
|
115
|
+
dropdown = createDropdown({ items: props.items, command: (item) => props.command({ id: item.id, label: item.label, type: item.type, slug: item.slug }) });
|
|
116
|
+
if (!props.clientRect) return;
|
|
117
|
+
popup = tippy('body', {
|
|
118
|
+
getReferenceClientRect: props.clientRect,
|
|
119
|
+
appendTo: () => document.body,
|
|
120
|
+
content: dropdown.el,
|
|
121
|
+
showOnCreate: true,
|
|
122
|
+
interactive: true,
|
|
123
|
+
trigger: 'manual',
|
|
124
|
+
placement: 'bottom-start',
|
|
125
|
+
}) as Instance<TippyProps>[];
|
|
126
|
+
},
|
|
127
|
+
onUpdate(props: any) {
|
|
128
|
+
dropdown.update(props.items, (item) => props.command({ id: item.id, label: item.label, type: item.type, slug: item.slug }));
|
|
129
|
+
if (props.clientRect && popup?.[0]) {
|
|
130
|
+
popup[0].setProps({ getReferenceClientRect: props.clientRect });
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
onKeyDown(props: any) {
|
|
134
|
+
if (props.event.key === 'Escape') { popup?.[0]?.hide(); return true; }
|
|
135
|
+
return dropdown.onKeyDown(props.event.key);
|
|
136
|
+
},
|
|
137
|
+
onExit() {
|
|
138
|
+
popup?.[0]?.destroy();
|
|
139
|
+
dropdown.el.remove();
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '$lib/utils/cn.js';
|
|
4
|
+
|
|
5
|
+
let { children, class: className }: { children: Snippet; class?: string } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div class={cn("w-full overflow-auto", className)}>
|
|
9
|
+
<table class="w-full caption-bottom text-sm">
|
|
10
|
+
{@render children()}
|
|
11
|
+
</table>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '$lib/utils/cn.js';
|
|
4
|
+
|
|
5
|
+
let { children, class: className }: { children: Snippet; class?: string } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<tbody class={cn("[&_tr:last-child]:border-0", className)}>
|
|
9
|
+
{@render children()}
|
|
10
|
+
</tbody>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '$lib/utils/cn.js';
|
|
4
|
+
|
|
5
|
+
let { children, class: className }: { children: Snippet; class?: string } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<caption class={cn("mt-4 text-xs text-muted-foreground", className)}>
|
|
9
|
+
{@render children()}
|
|
10
|
+
</caption>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '$lib/utils/cn.js';
|
|
4
|
+
|
|
5
|
+
let { children, class: className, ...rest }: { children?: Snippet; class?: string; [key: string]: unknown } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<td class={cn("px-4 py-2.5 align-middle [&:has([role=checkbox])]:pr-0", className)} {...rest}>
|
|
9
|
+
{@render children?.()}
|
|
10
|
+
</td>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '$lib/utils/cn.js';
|
|
4
|
+
|
|
5
|
+
let { children, class: className, ...rest }: { children?: Snippet; class?: string; [key: string]: unknown } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<th class={cn("h-9 px-4 text-left align-middle text-[10px] font-semibold uppercase tracking-wide text-muted-foreground [&:has([role=checkbox])]:pr-0", className)} {...rest}>
|
|
9
|
+
{@render children?.()}
|
|
10
|
+
</th>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '$lib/utils/cn.js';
|
|
4
|
+
|
|
5
|
+
let { children, class: className }: { children: Snippet; class?: string } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<thead class={cn("[&_tr]:border-b [&_tr]:border-border", className)}>
|
|
9
|
+
{@render children()}
|
|
10
|
+
</thead>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '$lib/utils/cn.js';
|
|
4
|
+
|
|
5
|
+
let { children, class: className }: { children: Snippet; class?: string } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<tr class={cn("border-b border-border transition-colors hover:bg-accent/50 data-[state=selected]:bg-accent", className)}>
|
|
9
|
+
{@render children()}
|
|
10
|
+
</tr>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as Table } from './Table.svelte';
|
|
2
|
+
export { default as TableHeader } from './TableHeader.svelte';
|
|
3
|
+
export { default as TableBody } from './TableBody.svelte';
|
|
4
|
+
export { default as TableRow } from './TableRow.svelte';
|
|
5
|
+
export { default as TableHead } from './TableHead.svelte';
|
|
6
|
+
export { default as TableCell } from './TableCell.svelte';
|
|
7
|
+
export { default as TableCaption } from './TableCaption.svelte';
|
package/src/lib/index.ts
CHANGED
|
@@ -242,3 +242,87 @@ export { default as Sparkline } from './components/ui/Sparkline.svelte';
|
|
|
242
242
|
|
|
243
243
|
// ProviderIcon
|
|
244
244
|
export { default as ProviderIcon } from './components/ui/ProviderIcon.svelte';
|
|
245
|
+
|
|
246
|
+
// RichEditor — full-featured TipTap editor (wiki, issues, comments)
|
|
247
|
+
export { default as RichEditor } from './components/ui/editor/RichEditor.svelte';
|
|
248
|
+
|
|
249
|
+
// CostDisplay — animated cost/currency display component
|
|
250
|
+
export { default as CostDisplay } from './components/ui/CostDisplay.svelte';
|
|
251
|
+
|
|
252
|
+
// StatusPill
|
|
253
|
+
export { default as StatusPill } from './components/ui/StatusPill.svelte';
|
|
254
|
+
|
|
255
|
+
// PageHeader
|
|
256
|
+
export { default as PageHeader } from './components/ui/PageHeader.svelte';
|
|
257
|
+
|
|
258
|
+
// Table
|
|
259
|
+
export {
|
|
260
|
+
Table,
|
|
261
|
+
TableHeader,
|
|
262
|
+
TableBody,
|
|
263
|
+
TableRow,
|
|
264
|
+
TableHead,
|
|
265
|
+
TableCell,
|
|
266
|
+
TableCaption,
|
|
267
|
+
} from './components/ui/table/index.js';
|
|
268
|
+
|
|
269
|
+
// Alert
|
|
270
|
+
export { default as Alert } from './components/ui/Alert.svelte';
|
|
271
|
+
|
|
272
|
+
// ListCard
|
|
273
|
+
export { default as ListCard } from './components/ui/ListCard.svelte';
|
|
274
|
+
|
|
275
|
+
// StatCard
|
|
276
|
+
export { default as StatCard } from './components/ui/StatCard.svelte';
|
|
277
|
+
|
|
278
|
+
// BranchPill
|
|
279
|
+
export { default as BranchPill } from './components/ui/BranchPill.svelte';
|
|
280
|
+
|
|
281
|
+
// CommentPill
|
|
282
|
+
export { default as CommentPill } from './components/ui/CommentPill.svelte';
|
|
283
|
+
|
|
284
|
+
// PermissionChips — Nucel-App permission badges
|
|
285
|
+
export { default as PermissionChips } from './components/ui/PermissionChips.svelte';
|
|
286
|
+
|
|
287
|
+
// AppCard — Nucel-App row card (marketplace, installed apps, owned apps)
|
|
288
|
+
export { default as AppCard } from './components/ui/AppCard.svelte';
|
|
289
|
+
|
|
290
|
+
// Kanban primitives — generic Trello-style column/card chrome.
|
|
291
|
+
// DnD wiring stays out: the consumer attaches svelte-dnd-action's
|
|
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';
|
|
296
|
+
|
|
297
|
+
// Section
|
|
298
|
+
export { default as Section } from './components/ui/Section.svelte';
|
|
299
|
+
|
|
300
|
+
// SectionTitle
|
|
301
|
+
export { default as SectionTitle } from './components/ui/SectionTitle.svelte';
|
|
302
|
+
|
|
303
|
+
// FormField
|
|
304
|
+
export { default as FormField } from './components/ui/FormField.svelte';
|
|
305
|
+
|
|
306
|
+
// Layout
|
|
307
|
+
export { default as AppShell } from './components/ui/AppShell.svelte';
|
|
308
|
+
export { default as AppSidebar } from './components/ui/AppSidebar.svelte';
|
|
309
|
+
export { default as NavItem } from './components/ui/NavItem.svelte';
|
|
310
|
+
export { default as NavSection } from './components/ui/NavSection.svelte';
|
|
311
|
+
|
|
312
|
+
// CopyButton
|
|
313
|
+
export { default as CopyButton } from './components/ui/CopyButton.svelte';
|
|
314
|
+
|
|
315
|
+
// Pagination
|
|
316
|
+
export { default as Pagination } from './components/ui/Pagination.svelte';
|
|
317
|
+
|
|
318
|
+
// CodeBlock
|
|
319
|
+
export { default as CodeBlock } from './components/ui/CodeBlock.svelte';
|
|
320
|
+
|
|
321
|
+
// Timeline
|
|
322
|
+
export { default as Timeline } from './components/ui/Timeline.svelte';
|
|
323
|
+
|
|
324
|
+
// FilterBar
|
|
325
|
+
export { default as FilterBar } from './components/ui/FilterBar.svelte';
|
|
326
|
+
|
|
327
|
+
// MetricCard
|
|
328
|
+
export { default as MetricCard } from './components/ui/MetricCard.svelte';
|
package/src/styles.css
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
--accent: oklch(0.967 0.001 286.375);
|
|
25
25
|
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
26
26
|
--destructive: oklch(0.577 0.245 27.325);
|
|
27
|
+
--success: oklch(0.527 0.154 150.069);
|
|
28
|
+
--warning: oklch(0.769 0.188 70.08);
|
|
27
29
|
--border: oklch(0.92 0.004 286.32);
|
|
28
30
|
--input: oklch(0.92 0.004 286.32);
|
|
29
31
|
--ring: oklch(0.705 0.015 286.067);
|
|
@@ -58,6 +60,8 @@
|
|
|
58
60
|
--accent: oklch(0.22 0.006 286.033);
|
|
59
61
|
--accent-foreground: oklch(0.97 0 0);
|
|
60
62
|
--destructive: oklch(0.704 0.191 22.216);
|
|
63
|
+
--success: oklch(0.627 0.168 150.069);
|
|
64
|
+
--warning: oklch(0.828 0.189 84.429);
|
|
61
65
|
--border: oklch(1 0 0 / 8%);
|
|
62
66
|
--input: oklch(1 0 0 / 12%);
|
|
63
67
|
--ring: oklch(0.552 0.016 285.938);
|
|
@@ -97,6 +101,8 @@
|
|
|
97
101
|
--color-accent: var(--accent);
|
|
98
102
|
--color-accent-foreground: var(--accent-foreground);
|
|
99
103
|
--color-destructive: var(--destructive);
|
|
104
|
+
--color-success: var(--success);
|
|
105
|
+
--color-warning: var(--warning);
|
|
100
106
|
--color-border: var(--border);
|
|
101
107
|
--color-input: var(--input);
|
|
102
108
|
--color-ring: var(--ring);
|