@nucel/ui 0.2.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.
Files changed (78) hide show
  1. package/package.json +8 -32
  2. package/src/lib/components/BottomSheet.svelte +96 -0
  3. package/src/lib/components/Breadcrumbs.svelte +57 -0
  4. package/src/lib/components/Checkbox.svelte +64 -0
  5. package/src/lib/components/CodeBlock.svelte +264 -0
  6. package/src/lib/components/CodeEditor.svelte +175 -0
  7. package/src/lib/components/ColorInput.svelte +41 -0
  8. package/src/lib/components/ColorInput.test.ts +126 -0
  9. package/src/lib/components/Combobox.svelte +103 -0
  10. package/src/lib/components/CommandPalette.svelte +135 -0
  11. package/src/lib/components/CopyButton.svelte +95 -0
  12. package/src/lib/components/CopyButton.test.ts +213 -0
  13. package/src/lib/components/DataTable.svelte +202 -0
  14. package/src/lib/components/DateRangePicker.svelte +185 -0
  15. package/src/lib/components/DiffEditor.svelte +174 -0
  16. package/src/lib/components/Drawer.svelte +69 -0
  17. package/src/lib/components/Fab.svelte +59 -0
  18. package/src/lib/components/Form.svelte +38 -0
  19. package/src/lib/components/FormField.svelte +51 -0
  20. package/src/lib/components/IconButton.svelte +86 -0
  21. package/src/lib/components/IconButton.test.ts +139 -0
  22. package/src/lib/components/InlineCode.svelte +28 -0
  23. package/src/lib/components/Pagination.svelte +65 -0
  24. package/src/lib/components/Radio.svelte +60 -0
  25. package/src/lib/components/RadioGroup.svelte +26 -0
  26. package/src/lib/components/SearchInput.svelte +77 -0
  27. package/src/lib/components/Skeleton.svelte +76 -0
  28. package/src/lib/components/StatCard.svelte +97 -0
  29. package/src/lib/components/ThemeProvider.svelte +157 -0
  30. package/src/lib/components/ThemeToggle.svelte +68 -0
  31. package/src/lib/components/ThreeWayMerge.svelte +185 -0
  32. package/src/lib/components/ui/MarkdownRenderer.svelte +126 -8
  33. package/src/lib/components/ui/Sparkline.svelte +1 -1
  34. package/src/lib/components/ui/StatusBadge.svelte +6 -3
  35. package/src/lib/components/ui/StatusDot.svelte +3 -3
  36. package/src/lib/index.ts +120 -45
  37. package/src/lib/utils/detectLanguage.ts +187 -0
  38. package/src/lib/utils/monaco-workers.d.ts +32 -0
  39. package/src/lib/utils/monacoLoader.ts +167 -0
  40. package/src/lib/utils/shikiHighlighter.ts +78 -0
  41. package/src/styles.css +100 -32
  42. package/src/lib/components/ui/Alert.svelte +0 -47
  43. package/src/lib/components/ui/Alert.test.ts +0 -206
  44. package/src/lib/components/ui/AppCard.svelte +0 -76
  45. package/src/lib/components/ui/AppShell.svelte +0 -14
  46. package/src/lib/components/ui/AppSidebar.svelte +0 -45
  47. package/src/lib/components/ui/BranchPill.svelte +0 -19
  48. package/src/lib/components/ui/BranchPill.test.ts +0 -121
  49. package/src/lib/components/ui/CommentPill.svelte +0 -12
  50. package/src/lib/components/ui/CostDisplay.svelte +0 -26
  51. package/src/lib/components/ui/CostDisplay.test.ts +0 -1115
  52. package/src/lib/components/ui/FormField.svelte +0 -34
  53. package/src/lib/components/ui/FormField.test.ts +0 -41
  54. package/src/lib/components/ui/ListCard.svelte +0 -9
  55. package/src/lib/components/ui/NavItem.svelte +0 -42
  56. package/src/lib/components/ui/NavSection.svelte +0 -17
  57. package/src/lib/components/ui/PageHeader.svelte +0 -25
  58. package/src/lib/components/ui/PageHeader.test.ts +0 -72
  59. package/src/lib/components/ui/PermissionChips.svelte +0 -49
  60. package/src/lib/components/ui/ProgressRing.test.ts +0 -239
  61. package/src/lib/components/ui/Section.svelte +0 -21
  62. package/src/lib/components/ui/Section.test.ts +0 -44
  63. package/src/lib/components/ui/SectionTitle.svelte +0 -16
  64. package/src/lib/components/ui/StatCard.svelte +0 -19
  65. package/src/lib/components/ui/StatusBadge.test.ts +0 -150
  66. package/src/lib/components/ui/StatusPill.svelte +0 -54
  67. package/src/lib/components/ui/StatusPill.test.ts +0 -125
  68. package/src/lib/components/ui/editor/RichEditor.svelte +0 -580
  69. package/src/lib/components/ui/editor/mention-suggestion.ts +0 -144
  70. package/src/lib/components/ui/table/Table.svelte +0 -12
  71. package/src/lib/components/ui/table/Table.test.ts +0 -317
  72. package/src/lib/components/ui/table/TableBody.svelte +0 -10
  73. package/src/lib/components/ui/table/TableCaption.svelte +0 -10
  74. package/src/lib/components/ui/table/TableCell.svelte +0 -10
  75. package/src/lib/components/ui/table/TableHead.svelte +0 -10
  76. package/src/lib/components/ui/table/TableHeader.svelte +0 -10
  77. package/src/lib/components/ui/table/TableRow.svelte +0 -10
  78. package/src/lib/components/ui/table/index.ts +0 -7
@@ -1,47 +0,0 @@
1
- <script lang="ts">
2
- import type { Component, Snippet } from 'svelte';
3
- import { cn } from '$lib/utils/cn.js';
4
-
5
- let {
6
- variant = 'default',
7
- title,
8
- children,
9
- icon: Icon,
10
- class: className,
11
- }: {
12
- variant?: 'default' | 'destructive' | 'success' | 'warning' | 'info';
13
- title?: string;
14
- children?: Snippet;
15
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
- icon?: any;
17
- class?: string;
18
- } = $props();
19
-
20
- const variantClasses: Record<string, string> = {
21
- default: 'border-border/60 bg-muted/40 text-foreground',
22
- destructive: 'border-destructive/30 bg-destructive/10 text-destructive',
23
- success: 'border-green-500/30 bg-green-500/10 text-green-600 dark:text-green-400',
24
- warning: 'border-yellow-500/30 bg-yellow-500/10 text-yellow-500',
25
- info: 'border-blue-500/30 bg-blue-500/10 text-blue-400',
26
- };
27
-
28
- const containerClass = $derived(
29
- cn(
30
- 'flex items-start gap-3 rounded-md border px-4 py-3 text-sm',
31
- variantClasses[variant],
32
- className,
33
- ),
34
- );
35
- </script>
36
-
37
- <div class={containerClass} role="alert">
38
- {#if Icon}
39
- <Icon class="mt-0.5 h-4 w-4 shrink-0" />
40
- {/if}
41
- <div>
42
- {#if title}
43
- <p class="font-semibold">{title}</p>
44
- {/if}
45
- {@render children?.()}
46
- </div>
47
- </div>
@@ -1,206 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { render, screen } from "@testing-library/svelte";
3
- import Alert from "./Alert.svelte";
4
-
5
- describe("Alert", () => {
6
- it("renders without crashing", () => {
7
- expect(() =>
8
- render(Alert, { props: { children: (() => {}) as any } }),
9
- ).not.toThrow();
10
- });
11
-
12
- it("renders a div with role=alert", () => {
13
- const { container } = render(Alert, {
14
- props: { children: (() => {}) as any },
15
- });
16
- expect(container.querySelector("[role=alert]")).not.toBeNull();
17
- });
18
-
19
- it("applies base flex classes", () => {
20
- const { container } = render(Alert, {
21
- props: { children: (() => {}) as any },
22
- });
23
- const el = container.querySelector("[role=alert]");
24
- expect(el!.className).toContain("flex");
25
- expect(el!.className).toContain("items-start");
26
- expect(el!.className).toContain("rounded-md");
27
- expect(el!.className).toContain("border");
28
- });
29
-
30
- it("applies default variant classes when no variant given", () => {
31
- const { container } = render(Alert, {
32
- props: { children: (() => {}) as any },
33
- });
34
- const el = container.querySelector("[role=alert]");
35
- expect(el!.className).toContain("bg-muted/40");
36
- expect(el!.className).toContain("text-foreground");
37
- });
38
-
39
- it("applies destructive variant classes", () => {
40
- const { container } = render(Alert, {
41
- props: { variant: "destructive", children: (() => {}) as any },
42
- });
43
- const el = container.querySelector("[role=alert]");
44
- expect(el!.className).toContain("text-destructive");
45
- expect(el!.className).toContain("bg-destructive/10");
46
- });
47
-
48
- it("applies success variant classes", () => {
49
- const { container } = render(Alert, {
50
- props: { variant: "success", children: (() => {}) as any },
51
- });
52
- const el = container.querySelector("[role=alert]");
53
- expect(el!.className).toContain("bg-green-500/10");
54
- expect(el!.className).toContain("text-green-600");
55
- });
56
-
57
- it("applies warning variant classes", () => {
58
- const { container } = render(Alert, {
59
- props: { variant: "warning", children: (() => {}) as any },
60
- });
61
- const el = container.querySelector("[role=alert]");
62
- expect(el!.className).toContain("bg-yellow-500/10");
63
- expect(el!.className).toContain("text-yellow-500");
64
- });
65
-
66
- it("applies info variant classes", () => {
67
- const { container } = render(Alert, {
68
- props: { variant: "info", children: (() => {}) as any },
69
- });
70
- const el = container.querySelector("[role=alert]");
71
- expect(el!.className).toContain("bg-blue-500/10");
72
- expect(el!.className).toContain("text-blue-400");
73
- });
74
-
75
- it("renders title text when title prop is provided", () => {
76
- render(Alert, {
77
- props: { title: "Heads up!", children: (() => {}) as any },
78
- });
79
- expect(screen.getByText("Heads up!")).toBeInTheDocument();
80
- });
81
-
82
- it("title element has font-semibold class", () => {
83
- const { container } = render(Alert, {
84
- props: { title: "My Title", children: (() => {}) as any },
85
- });
86
- const p = container.querySelector("p");
87
- expect(p).not.toBeNull();
88
- expect(p!.className).toContain("font-semibold");
89
- });
90
-
91
- it("does not render a p element when title is not provided", () => {
92
- const { container } = render(Alert, {
93
- props: { children: (() => {}) as any },
94
- });
95
- expect(container.querySelector("p")).toBeNull();
96
- });
97
-
98
- it("merges extra class prop with base classes", () => {
99
- const { container } = render(Alert, {
100
- props: { children: (() => {}) as any, class: "my-extra-class" },
101
- });
102
- const el = container.querySelector("[role=alert]");
103
- expect(el!.className).toContain("my-extra-class");
104
- expect(el!.className).toContain("rounded-md");
105
- });
106
-
107
- it("does not render an svg/icon when icon prop is not provided", () => {
108
- const { container } = render(Alert, {
109
- props: { children: (() => {}) as any },
110
- });
111
- expect(container.querySelector("svg")).toBeNull();
112
- });
113
-
114
- it("destructive variant has border-destructive/30 class", () => {
115
- const { container } = render(Alert, {
116
- props: { variant: "destructive", children: (() => {}) as any },
117
- });
118
- const el = container.querySelector("[role=alert]");
119
- expect(el!.className).toContain("border-destructive/30");
120
- });
121
-
122
- it("info variant has border-blue-500/30 class", () => {
123
- const { container } = render(Alert, {
124
- props: { variant: "info", children: (() => {}) as any },
125
- });
126
- const el = container.querySelector("[role=alert]");
127
- expect(el!.className).toContain("border-blue-500/30");
128
- });
129
-
130
- it("warning variant has border-yellow-500/30 class", () => {
131
- const { container } = render(Alert, {
132
- props: { variant: "warning", children: (() => {}) as any },
133
- });
134
- const el = container.querySelector("[role=alert]");
135
- expect(el!.className).toContain("border-yellow-500/30");
136
- });
137
-
138
- it("success variant has border-green-500/30 class", () => {
139
- const { container } = render(Alert, {
140
- props: { variant: "success", children: (() => {}) as any },
141
- });
142
- const el = container.querySelector("[role=alert]");
143
- expect(el!.className).toContain("border-green-500/30");
144
- });
145
-
146
- it("default variant has border-border/60 class", () => {
147
- const { container } = render(Alert, {
148
- props: { children: (() => {}) as any },
149
- });
150
- const el = container.querySelector("[role=alert]");
151
- expect(el!.className).toContain("border-border/60");
152
- });
153
-
154
- it("renders without crashing when neither children nor title given", () => {
155
- expect(() => render(Alert, { props: {} })).not.toThrow();
156
- });
157
-
158
- it("root element tag is div", () => {
159
- const { container } = render(Alert, {
160
- props: { children: (() => {}) as any },
161
- });
162
- const el = container.querySelector("[role=alert]");
163
- expect(el!.tagName.toLowerCase()).toBe("div");
164
- });
165
-
166
- it("root element has gap-3 class", () => {
167
- const { container } = render(Alert, {
168
- props: { children: (() => {}) as any },
169
- });
170
- const el = container.querySelector("[role=alert]");
171
- expect(el!.className).toContain("gap-3");
172
- });
173
-
174
- it("root element has px-4 class", () => {
175
- const { container } = render(Alert, {
176
- props: { children: (() => {}) as any },
177
- });
178
- const el = container.querySelector("[role=alert]");
179
- expect(el!.className).toContain("px-4");
180
- });
181
-
182
- it("root element has py-3 class", () => {
183
- const { container } = render(Alert, {
184
- props: { children: (() => {}) as any },
185
- });
186
- const el = container.querySelector("[role=alert]");
187
- expect(el!.className).toContain("py-3");
188
- });
189
-
190
- it("root element has text-sm class", () => {
191
- const { container } = render(Alert, {
192
- props: { children: (() => {}) as any },
193
- });
194
- const el = container.querySelector("[role=alert]");
195
- expect(el!.className).toContain("text-sm");
196
- });
197
-
198
- it("does not lose variant classes when extra class is added", () => {
199
- const { container } = render(Alert, {
200
- props: { variant: "destructive", children: (() => {}) as any, class: "extra" },
201
- });
202
- const el = container.querySelector("[role=alert]");
203
- expect(el!.className).toContain("text-destructive");
204
- expect(el!.className).toContain("extra");
205
- });
206
- });
@@ -1,76 +0,0 @@
1
- <script lang="ts">
2
- // Reusable card for a Nucel-App row. Used by:
3
- // - Settings/Developer/Apps/Index (developer's owned apps)
4
- // - Marketplace/Index (public apps)
5
- // - Org/InstalledApps (apps installed on an org)
6
- //
7
- // The data model is intentionally flat — callers pass exactly what
8
- // they want rendered and slot any actions (Install, Suspend, Uninstall,
9
- // ChevronRight, etc.) into the trailing `actions` snippet.
10
- //
11
- // `meta` and `subtitle` are optional secondary lines so each call site
12
- // can attach its own context (e.g. "by @owner-org · published 5d ago"
13
- // for marketplace; "installed as deploy-bot[bot] · 3h ago" for org).
14
-
15
- import { Bot } from '@lucide/svelte';
16
- import PermissionChips from './PermissionChips.svelte';
17
- import type { Snippet } from 'svelte';
18
-
19
- let {
20
- name,
21
- href,
22
- icon_url = null,
23
- subtitle = null,
24
- meta = null,
25
- permissions = null,
26
- actions,
27
- badges,
28
- }: {
29
- name: string;
30
- href?: string;
31
- icon_url?: string | null;
32
- subtitle?: string | null;
33
- meta?: Snippet | null;
34
- permissions?: Record<string, string> | null;
35
- actions?: Snippet;
36
- badges?: Snippet;
37
- } = $props();
38
- </script>
39
-
40
- <div class="flex items-start gap-3 px-4 py-4">
41
- <div class="flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-primary/10 text-primary">
42
- {#if icon_url}
43
- <img src={icon_url} alt="" class="h-full w-full object-cover" />
44
- {:else}
45
- <Bot class="h-4 w-4" />
46
- {/if}
47
- </div>
48
-
49
- <div class="min-w-0 flex-1">
50
- <div class="flex items-center gap-2 flex-wrap">
51
- {#if href}
52
- <a class="text-sm font-medium text-foreground hover:text-primary hover:underline" {href}>{name}</a>
53
- {:else}
54
- <span class="text-sm font-medium text-foreground">{name}</span>
55
- {/if}
56
- {#if badges}{@render badges()}{/if}
57
- </div>
58
- {#if subtitle}
59
- <p class="mt-0.5 text-xs text-muted-foreground">{subtitle}</p>
60
- {/if}
61
- {#if meta}
62
- <div class="mt-1 text-[11px] text-muted-foreground">{@render meta()}</div>
63
- {/if}
64
- {#if permissions}
65
- <div class="mt-2">
66
- <PermissionChips {permissions} compact />
67
- </div>
68
- {/if}
69
- </div>
70
-
71
- {#if actions}
72
- <div class="flex shrink-0 flex-col items-end gap-2">
73
- {@render actions()}
74
- </div>
75
- {/if}
76
- </div>
@@ -1,14 +0,0 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
- let { sidebar, header, children }: { sidebar?: Snippet; header?: Snippet; children?: Snippet } = $props();
4
- </script>
5
-
6
- <div class="flex h-screen overflow-hidden bg-background">
7
- {#if sidebar}{@render sidebar()}{/if}
8
- <div class="flex flex-1 flex-col overflow-hidden">
9
- {#if header}<header class="flex h-12 shrink-0 items-center border-b border-border bg-background px-4">{@render header()}</header>{/if}
10
- <main class="flex-1 overflow-y-auto p-6">
11
- {@render children?.()}
12
- </main>
13
- </div>
14
- </div>
@@ -1,45 +0,0 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
- import { ChevronLeft, ChevronRight } from '@lucide/svelte';
4
-
5
- let {
6
- collapsed = $bindable(false),
7
- brand,
8
- nav,
9
- footer,
10
- }: {
11
- collapsed?: boolean;
12
- brand?: Snippet<[{ collapsed: boolean }]>;
13
- nav?: Snippet<[{ collapsed: boolean }]>;
14
- footer?: Snippet<[{ collapsed: boolean }]>;
15
- } = $props();
16
- </script>
17
-
18
- <aside
19
- class="flex flex-col border-r border-sidebar-border bg-sidebar transition-[width] duration-200"
20
- style:width={collapsed ? '56px' : '224px'}
21
- style:min-width={collapsed ? '56px' : '224px'}
22
- >
23
- <div class="flex h-12 shrink-0 items-center border-b border-sidebar-border px-3">
24
- <div class="flex-1 overflow-hidden">
25
- {#if brand}{@render brand({ collapsed })}{/if}
26
- </div>
27
- <button
28
- class="flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:text-foreground"
29
- onclick={() => (collapsed = !collapsed)}
30
- aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
31
- >
32
- {#if collapsed}<ChevronRight size={14} />{:else}<ChevronLeft size={14} />{/if}
33
- </button>
34
- </div>
35
-
36
- <nav class="flex flex-1 flex-col gap-0.5 overflow-y-auto p-2">
37
- {#if nav}{@render nav({ collapsed })}{/if}
38
- </nav>
39
-
40
- {#if footer}
41
- <div class="border-t border-sidebar-border p-2">
42
- {@render footer({ collapsed })}
43
- </div>
44
- {/if}
45
- </aside>
@@ -1,19 +0,0 @@
1
- <script lang="ts">
2
- import { GitBranch } from '@lucide/svelte';
3
-
4
- let {
5
- name,
6
- size = 'sm',
7
- }: {
8
- name: string;
9
- size?: 'xs' | 'sm';
10
- } = $props();
11
-
12
- let sizeCls = $derived(size === 'xs' ? 'text-[10px] px-1.5 py-0.5' : 'text-[11px] px-2 py-0.5');
13
- let iconSize = $derived(size === 'xs' ? 'h-2.5 w-2.5' : 'h-3 w-3');
14
- </script>
15
-
16
- <span class="inline-flex items-center gap-1 rounded-md border border-border/60 bg-background font-mono text-foreground/80 {sizeCls}">
17
- <GitBranch class={iconSize} />
18
- {name}
19
- </span>
@@ -1,121 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { render, screen } from "@testing-library/svelte";
3
- import BranchPill from "./BranchPill.svelte";
4
-
5
- describe("BranchPill — rendering", () => {
6
- it("renders the branch name", () => {
7
- render(BranchPill, { props: { name: "main" } });
8
- expect(screen.getByText("main")).toBeInTheDocument();
9
- });
10
-
11
- it("renders a different branch name", () => {
12
- render(BranchPill, { props: { name: "feature/my-feature" } });
13
- expect(screen.getByText("feature/my-feature")).toBeInTheDocument();
14
- });
15
-
16
- it("renders an svg element (git branch icon)", () => {
17
- const { container } = render(BranchPill, { props: { name: "main" } });
18
- expect(container.querySelector("svg")).not.toBeNull();
19
- });
20
-
21
- it("renders without crashing", () => {
22
- expect(() => render(BranchPill, { props: { name: "develop" } })).not.toThrow();
23
- });
24
-
25
- it("root element is a span", () => {
26
- const { container } = render(BranchPill, { props: { name: "main" } });
27
- const el = container.firstElementChild;
28
- expect(el?.tagName.toLowerCase()).toBe("span");
29
- });
30
- });
31
-
32
- describe("BranchPill — classes", () => {
33
- it("applies inline-flex class", () => {
34
- const { container } = render(BranchPill, { props: { name: "main" } });
35
- const span = container.querySelector("span");
36
- expect(span?.className).toContain("inline-flex");
37
- });
38
-
39
- it("applies items-center class", () => {
40
- const { container } = render(BranchPill, { props: { name: "main" } });
41
- const span = container.querySelector("span");
42
- expect(span?.className).toContain("items-center");
43
- });
44
-
45
- it("applies font-mono class", () => {
46
- const { container } = render(BranchPill, { props: { name: "main" } });
47
- const span = container.querySelector("span");
48
- expect(span?.className).toContain("font-mono");
49
- });
50
-
51
- it("applies rounded-md class", () => {
52
- const { container } = render(BranchPill, { props: { name: "main" } });
53
- const span = container.querySelector("span");
54
- expect(span?.className).toContain("rounded-md");
55
- });
56
-
57
- it("has border class", () => {
58
- const { container } = render(BranchPill, { props: { name: "main" } });
59
- const span = container.querySelector("span");
60
- expect(span?.className).toContain("border");
61
- });
62
-
63
- it("size='sm' (default) applies text-[11px]", () => {
64
- const { container } = render(BranchPill, { props: { name: "main" } });
65
- const span = container.querySelector("span");
66
- expect(span?.className).toContain("text-[11px]");
67
- });
68
-
69
- it("size='sm' applies px-2", () => {
70
- const { container } = render(BranchPill, { props: { name: "main", size: "sm" } });
71
- const span = container.querySelector("span");
72
- expect(span?.className).toContain("px-2");
73
- });
74
-
75
- it("size='xs' applies text-[10px]", () => {
76
- const { container } = render(BranchPill, { props: { name: "main", size: "xs" } });
77
- const span = container.querySelector("span");
78
- expect(span?.className).toContain("text-[10px]");
79
- });
80
-
81
- it("size='xs' applies px-1.5", () => {
82
- const { container } = render(BranchPill, { props: { name: "main", size: "xs" } });
83
- const span = container.querySelector("span");
84
- expect(span?.className).toContain("px-1.5");
85
- });
86
-
87
- it("size='xs' does not apply text-[11px]", () => {
88
- const { container } = render(BranchPill, { props: { name: "main", size: "xs" } });
89
- const span = container.querySelector("span");
90
- expect(span?.className).not.toContain("text-[11px]");
91
- });
92
-
93
- it("size='sm' does not apply text-[10px]", () => {
94
- const { container } = render(BranchPill, { props: { name: "main", size: "sm" } });
95
- const span = container.querySelector("span");
96
- expect(span?.className).not.toContain("text-[10px]");
97
- });
98
- });
99
-
100
- describe("BranchPill — edge cases", () => {
101
- it("renders branch names with slashes", () => {
102
- render(BranchPill, { props: { name: "feat/TICKET-123/add-auth" } });
103
- expect(screen.getByText("feat/TICKET-123/add-auth")).toBeInTheDocument();
104
- });
105
-
106
- it("renders very long branch names", () => {
107
- const longName = "feature/very-long-branch-name-that-is-quite-descriptive";
108
- render(BranchPill, { props: { name: longName } });
109
- expect(screen.getByText(longName)).toBeInTheDocument();
110
- });
111
-
112
- it("renders branch name with numbers", () => {
113
- render(BranchPill, { props: { name: "release/1.2.3" } });
114
- expect(screen.getByText("release/1.2.3")).toBeInTheDocument();
115
- });
116
-
117
- it("renders both xs and sm without throwing", () => {
118
- expect(() => render(BranchPill, { props: { name: "main", size: "xs" } })).not.toThrow();
119
- expect(() => render(BranchPill, { props: { name: "main", size: "sm" } })).not.toThrow();
120
- });
121
- });
@@ -1,12 +0,0 @@
1
- <script lang="ts">
2
- import { MessageCircle } from '@lucide/svelte';
3
-
4
- let { count }: { count: number } = $props();
5
- </script>
6
-
7
- {#if count > 0}
8
- <div class="flex shrink-0 items-center gap-1 rounded-md border border-border/60 px-2 py-1 text-xs text-muted-foreground">
9
- <MessageCircle class="h-3 w-3" />
10
- <span>{count}</span>
11
- </div>
12
- {/if}
@@ -1,26 +0,0 @@
1
- <script lang="ts">
2
- import NumberFlow from "@number-flow/svelte";
3
-
4
- let {
5
- amount,
6
- precision = 2,
7
- size = "sm",
8
- class: className = "",
9
- }: {
10
- amount: number;
11
- precision?: 2 | 3 | 4;
12
- size?: "xs" | "sm";
13
- class?: string;
14
- } = $props();
15
-
16
- const sizeClass = $derived(
17
- size === "xs" ? "text-[10px]" : "text-xs",
18
- );
19
- </script>
20
-
21
- <span class="tabular-nums text-muted-foreground {sizeClass} {className}">
22
- <NumberFlow
23
- value={amount}
24
- format={{ style: "currency", currency: "USD", minimumFractionDigits: precision, maximumFractionDigits: precision }}
25
- />
26
- </span>