@nucel/ui 0.10.0 → 0.12.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 (30) hide show
  1. package/README.md +21 -1
  2. package/package.json +29 -5
  3. package/src/lib/components/ui/Alert.svelte +47 -0
  4. package/src/lib/components/ui/AppCard.svelte +76 -0
  5. package/src/lib/components/ui/BranchPill.svelte +19 -0
  6. package/src/lib/components/ui/CommentPill.svelte +12 -0
  7. package/src/lib/components/ui/KanbanBoard.svelte +27 -0
  8. package/src/lib/components/ui/KanbanCard.svelte +43 -0
  9. package/src/lib/components/ui/KanbanColumn.svelte +52 -0
  10. package/src/lib/components/ui/ListCard.svelte +9 -0
  11. package/src/lib/components/ui/PageHeader.svelte +25 -0
  12. package/src/lib/components/ui/PermissionChips.svelte +49 -0
  13. package/src/lib/components/ui/Section.svelte +21 -0
  14. package/src/lib/components/ui/SectionTitle.svelte +16 -0
  15. package/src/lib/components/ui/StatusPill.svelte +54 -0
  16. package/src/lib/components/ui/editor/RichEditor.svelte +580 -0
  17. package/src/lib/components/ui/editor/mention-suggestion.ts +144 -0
  18. package/src/lib/components/ui/table/Table.svelte +12 -0
  19. package/src/lib/components/ui/table/TableBody.svelte +10 -0
  20. package/src/lib/components/ui/table/TableCaption.svelte +10 -0
  21. package/src/lib/components/ui/table/TableCell.svelte +10 -0
  22. package/src/lib/components/ui/table/TableHead.svelte +10 -0
  23. package/src/lib/components/ui/table/TableHeader.svelte +10 -0
  24. package/src/lib/components/ui/table/TableRow.svelte +10 -0
  25. package/src/lib/components/ui/table/index.ts +7 -0
  26. package/src/lib/index.ts +50 -0
  27. package/src/lib/components/ColorInput.test.ts +0 -126
  28. package/src/lib/components/CopyButton.test.ts +0 -213
  29. package/src/lib/components/IconButton.test.ts +0 -139
  30. package/src/lib/utils/cn.test.ts +0 -993
package/README.md CHANGED
@@ -217,9 +217,21 @@ bun run storybook
217
217
  # Install dependencies
218
218
  bun install
219
219
 
220
- # Run checks
220
+ # Build = export-completeness guard + svelte-check (MUST pass before commit/publish)
221
+ bun run build
222
+
223
+ # Type-check only
221
224
  bun run check
222
225
 
226
+ # Export-completeness guard standalone — fails if any component under
227
+ # src/lib/components is not reachable from the barrel (src/lib/index.ts).
228
+ # This prevents the regression class where a component silently drops out
229
+ # of the public surface and breaks the consuming app's build.
230
+ bun run check:exports
231
+
232
+ # Run the unit/component test suite (Vitest + Testing Library)
233
+ bun run test
234
+
223
235
  # Run linter
224
236
  bun run lint
225
237
 
@@ -230,6 +242,14 @@ bun run format
230
242
  bun run build-storybook
231
243
  ```
232
244
 
245
+ ### Adding a component
246
+
247
+ When you add a `.svelte` component under `src/lib/components`, you **must** also
248
+ export it from `src/lib/index.ts`. `bun run build` (and CI / `prepublishOnly`)
249
+ run `check:exports`, which fails the build if any component file is not
250
+ reachable from the barrel. If a file is intentionally internal, list it in the
251
+ `IGNORE` set in `scripts/check-exports.mjs`.
252
+
233
253
  ## License
234
254
 
235
255
  MIT © Nucel Team
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nucel/ui",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "A comprehensive Svelte 5 UI component library for Nucel projects",
5
5
  "type": "module",
6
6
  "svelte": "./src/lib/index.ts",
@@ -13,12 +13,15 @@
13
13
  },
14
14
  "files": [
15
15
  "src/lib",
16
- "src/styles.css"
16
+ "src/styles.css",
17
+ "!src/lib/**/*.test.ts",
18
+ "!src/lib/**/*.test.js"
17
19
  ],
18
20
  "scripts": {
19
21
  "dev": "vite",
20
- "build": "svelte-check --tsconfig ./tsconfig.json",
22
+ "build": "node scripts/check-exports.mjs && svelte-check --tsconfig ./tsconfig.json",
21
23
  "check": "svelte-check --tsconfig ./tsconfig.json",
24
+ "check:exports": "node scripts/check-exports.mjs",
22
25
  "lint": "eslint .",
23
26
  "format": "prettier --write .",
24
27
  "format:check": "prettier --check .",
@@ -26,7 +29,7 @@
26
29
  "test:watch": "vitest",
27
30
  "storybook": "storybook dev -p 6006",
28
31
  "build-storybook": "storybook build",
29
- "prepublishOnly": "echo 'publishing @nucel/ui'"
32
+ "prepublishOnly": "node scripts/check-exports.mjs && echo 'publishing @nucel/ui'"
30
33
  },
31
34
  "repository": {
32
35
  "type": "git",
@@ -46,6 +49,26 @@
46
49
  "license": "MIT",
47
50
  "dependencies": {
48
51
  "@lucide/svelte": "^0.564.0",
52
+ "@tiptap/core": "^3.23.6",
53
+ "@tiptap/extension-character-count": "^3.23.6",
54
+ "@tiptap/extension-color": "^3.23.6",
55
+ "@tiptap/extension-highlight": "^3.23.6",
56
+ "@tiptap/extension-link": "^3.23.6",
57
+ "@tiptap/extension-mention": "^3.23.6",
58
+ "@tiptap/extension-placeholder": "^3.23.6",
59
+ "@tiptap/extension-subscript": "^3.23.6",
60
+ "@tiptap/extension-superscript": "^3.23.6",
61
+ "@tiptap/extension-table": "^3.23.6",
62
+ "@tiptap/extension-table-cell": "^3.23.6",
63
+ "@tiptap/extension-table-header": "^3.23.6",
64
+ "@tiptap/extension-table-row": "^3.23.6",
65
+ "@tiptap/extension-task-item": "^3.23.6",
66
+ "@tiptap/extension-task-list": "^3.23.6",
67
+ "@tiptap/extension-text-align": "^3.23.6",
68
+ "@tiptap/extension-text-style": "^3.23.6",
69
+ "@tiptap/extension-typography": "^3.23.6",
70
+ "@tiptap/extension-underline": "^3.23.6",
71
+ "@tiptap/starter-kit": "^3.23.6",
49
72
  "bits-ui": "^2.16.1",
50
73
  "clsx": "^2.1.1",
51
74
  "dompurify": "^3.3.3",
@@ -54,7 +77,8 @@
54
77
  "shiki": "^4.1.0",
55
78
  "svelte-sonner": "^1.0.7",
56
79
  "tailwind-merge": "^3.5.0",
57
- "tailwind-variants": "^3.2.2"
80
+ "tailwind-variants": "^3.2.2",
81
+ "tippy.js": "^6.3.7"
58
82
  },
59
83
  "devDependencies": {
60
84
  "@eslint/js": "^9.39.2",
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ import type { Component, Snippet } from 'svelte';
3
+ import { cn } from '../../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>
@@ -0,0 +1,76 @@
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>
@@ -0,0 +1,19 @@
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>
@@ -0,0 +1,12 @@
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}
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ // Outer kanban container: a horizontally-scrollable row of columns plus
3
+ // an optional trailing slot for an "Add column" affordance.
4
+ //
5
+ // Composition: caller puts <KanbanColumn> elements (or anything else)
6
+ // into the default slot. DnD wiring stays out of this primitive — the
7
+ // consumer attaches dndzone to whichever container they want sortable.
8
+
9
+ import type { Snippet } from 'svelte';
10
+
11
+ let {
12
+ children,
13
+ trailing,
14
+ }: {
15
+ children: Snippet;
16
+ trailing?: Snippet;
17
+ } = $props();
18
+ </script>
19
+
20
+ <div class="flex items-start gap-3 overflow-x-auto pb-4">
21
+ {@render children()}
22
+ {#if trailing}
23
+ <div class="w-72 flex-shrink-0">
24
+ {@render trailing()}
25
+ </div>
26
+ {/if}
27
+ </div>
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ // Minimal card for a kanban column. Title is required; body is optional
3
+ // single-paragraph supplementary text. `onDelete` adds a hover-revealed
4
+ // X button. `extra` is a snippet for arbitrary chrome (badges, avatars,
5
+ // etc.) under the body.
6
+
7
+ import { X } from '@lucide/svelte';
8
+ import type { Snippet } from 'svelte';
9
+
10
+ let {
11
+ title,
12
+ body = null,
13
+ onDelete,
14
+ extra,
15
+ }: {
16
+ title: string;
17
+ body?: string | null;
18
+ onDelete?: () => void;
19
+ extra?: Snippet;
20
+ } = $props();
21
+ </script>
22
+
23
+ <div class="group rounded-md border border-border bg-background p-2.5 text-sm shadow-sm">
24
+ <div class="flex items-start justify-between gap-1">
25
+ <span class="leading-snug">{title}</span>
26
+ {#if onDelete}
27
+ <button
28
+ type="button"
29
+ aria-label="Delete card"
30
+ class="opacity-0 transition-opacity group-hover:opacity-100"
31
+ onclick={onDelete}
32
+ >
33
+ <X class="h-3 w-3 text-muted-foreground hover:text-destructive" />
34
+ </button>
35
+ {/if}
36
+ </div>
37
+ {#if body}
38
+ <p class="mt-1 line-clamp-2 text-xs text-muted-foreground">{body}</p>
39
+ {/if}
40
+ {#if extra}
41
+ <div class="mt-1.5">{@render extra()}</div>
42
+ {/if}
43
+ </div>
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ // A single kanban column: header (title + count + optional delete),
3
+ // a body snippet for cards (where the caller attaches a dndzone), and
4
+ // a footer snippet for the per-column "Add card" affordance.
5
+
6
+ import { X } from '@lucide/svelte';
7
+ import type { Snippet } from 'svelte';
8
+
9
+ let {
10
+ name,
11
+ count,
12
+ onDelete,
13
+ body,
14
+ footer,
15
+ }: {
16
+ name: string;
17
+ count: number;
18
+ /** When provided, renders a small delete-X in the header. */
19
+ onDelete?: () => void;
20
+ /** Sortable body — caller drops their dndzone-decorated container here. */
21
+ body: Snippet;
22
+ /** Optional footer (e.g. "+ Add card"). */
23
+ footer?: Snippet;
24
+ } = $props();
25
+ </script>
26
+
27
+ <div class="w-72 flex-shrink-0 rounded-lg bg-muted/50 p-3">
28
+ <div class="mb-2 flex items-center justify-between">
29
+ <h3 class="text-sm font-semibold">{name}</h3>
30
+ <div class="flex items-center gap-1">
31
+ <span class="rounded-full bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">
32
+ {count}
33
+ </span>
34
+ {#if onDelete}
35
+ <button
36
+ type="button"
37
+ aria-label={`Delete column ${name}`}
38
+ class="rounded p-0.5 text-muted-foreground hover:bg-background hover:text-destructive"
39
+ onclick={onDelete}
40
+ >
41
+ <X class="h-3 w-3" />
42
+ </button>
43
+ {/if}
44
+ </div>
45
+ </div>
46
+
47
+ {@render body()}
48
+
49
+ {#if footer}
50
+ {@render footer()}
51
+ {/if}
52
+ </div>
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ let { children }: { children: Snippet } = $props();
5
+ </script>
6
+
7
+ <div class="overflow-hidden rounded-xl border border-border bg-card divide-y divide-border">
8
+ {@render children()}
9
+ </div>
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ let {
5
+ title,
6
+ subtitle,
7
+ actions,
8
+ }: {
9
+ title: string;
10
+ subtitle?: string;
11
+ actions?: Snippet;
12
+ } = $props();
13
+ </script>
14
+
15
+ <div class="mb-5 flex items-start justify-between gap-4">
16
+ <div class="min-w-0">
17
+ <h1 class="text-lg font-semibold tracking-tight text-foreground">{title}</h1>
18
+ {#if subtitle}
19
+ <p class="mt-0.5 text-xs text-muted-foreground">{subtitle}</p>
20
+ {/if}
21
+ </div>
22
+ {#if actions}
23
+ <div class="flex shrink-0 items-center gap-2">{@render actions()}</div>
24
+ {/if}
25
+ </div>
@@ -0,0 +1,49 @@
1
+ <script lang="ts">
2
+ // Compact permission chips for Nucel-App permission sets.
3
+ //
4
+ // Used by the marketplace card, installed-apps card, and the
5
+ // developer's per-app Show page. Filters out `none` automatically and
6
+ // sorts alphabetically so the visual order is stable across calls.
7
+ //
8
+ // `compact=true` (default) renders single-line `key:level` chips
9
+ // (used inside cards). `compact=false` renders a per-row list with
10
+ // primary-tinted level badges (used on the Show page).
11
+
12
+ let { permissions, compact = true }: {
13
+ permissions: Record<string, string> | null | undefined;
14
+ compact?: boolean;
15
+ } = $props();
16
+
17
+ const entries = $derived(
18
+ Object.entries(permissions ?? {})
19
+ .filter(([_, v]) => typeof v === 'string' && v !== 'none')
20
+ .sort(([a], [b]) => a.localeCompare(b)),
21
+ );
22
+ </script>
23
+
24
+ {#if entries.length === 0}
25
+ <span class="text-[11px] italic text-muted-foreground">No write access requested.</span>
26
+ {:else if compact}
27
+ <div class="flex flex-wrap gap-1">
28
+ {#each entries as [key, level]}
29
+ <span class="rounded-md border border-border/60 bg-muted/40 px-1.5 py-0.5 text-[11px]">
30
+ <code class="font-mono">{key}</code>:{level}
31
+ </span>
32
+ {/each}
33
+ </div>
34
+ {:else}
35
+ <ul class="space-y-1.5">
36
+ {#each entries as [key, level]}
37
+ <li class="flex items-center justify-between gap-3 rounded-md border border-border/60 bg-muted/30 px-3 py-1.5 text-xs">
38
+ <code class="font-mono">{key}</code>
39
+ <span
40
+ class="rounded-md border px-1.5 py-0.5 text-[11px] {level === 'write' || level === 'admin'
41
+ ? 'border-primary/40 bg-primary/10 text-primary'
42
+ : 'border-border/60 bg-muted text-muted-foreground'}"
43
+ >
44
+ {level}
45
+ </span>
46
+ </li>
47
+ {/each}
48
+ </ul>
49
+ {/if}
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { cn } from '../../utils/cn.js';
4
+
5
+ let {
6
+ title,
7
+ children,
8
+ class: className,
9
+ }: {
10
+ title?: string;
11
+ children?: Snippet;
12
+ class?: string;
13
+ } = $props();
14
+ </script>
15
+
16
+ <div class={cn('rounded-xl border border-border bg-card p-5', className)}>
17
+ {#if title}
18
+ <h3 class="mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground">{title}</h3>
19
+ {/if}
20
+ {@render children?.()}
21
+ </div>
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { cn } from '../../utils/cn.js';
4
+
5
+ let {
6
+ children,
7
+ class: className,
8
+ }: {
9
+ children: Snippet;
10
+ class?: string;
11
+ } = $props();
12
+ </script>
13
+
14
+ <h3 class={cn('mb-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground', className)}>
15
+ {@render children()}
16
+ </h3>
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import { Check, X, Loader2, CircleSlash, Clock, AlertTriangle } from '@lucide/svelte';
3
+
4
+ type Status = 'success' | 'failure' | 'running' | 'pending' | 'cancelled' | 'warning';
5
+
6
+ let {
7
+ status,
8
+ label,
9
+ size = 'sm',
10
+ }: {
11
+ status: Status;
12
+ label?: string;
13
+ size?: 'xs' | 'sm';
14
+ } = $props();
15
+
16
+ const defaults: Record<Status, string> = {
17
+ success: 'passed',
18
+ failure: 'failed',
19
+ running: 'running',
20
+ pending: 'pending',
21
+ cancelled: 'cancelled',
22
+ warning: 'warning',
23
+ };
24
+
25
+ const styles: Record<Status, string> = {
26
+ success: 'border-green-500/30 bg-green-500/10 text-green-500',
27
+ failure: 'border-destructive/30 bg-destructive/10 text-destructive',
28
+ running: 'border-blue-500/30 bg-blue-500/10 text-blue-400',
29
+ pending: 'border-yellow-500/30 bg-yellow-500/10 text-yellow-500',
30
+ cancelled: 'border-border/60 bg-muted text-muted-foreground',
31
+ warning: 'border-amber-500/30 bg-amber-500/10 text-amber-500',
32
+ };
33
+
34
+ let text = $derived(label ?? defaults[status]);
35
+ let sizeCls = $derived(size === 'xs' ? 'text-[10px] px-1.5 py-0.5' : 'text-[11px] px-2 py-0.5');
36
+ let iconSize = $derived(size === 'xs' ? 'h-2.5 w-2.5' : 'h-3 w-3');
37
+ </script>
38
+
39
+ <span class="inline-flex items-center gap-1 rounded-full border font-medium {sizeCls} {styles[status]}">
40
+ {#if status === 'success'}
41
+ <Check class={iconSize} />
42
+ {:else if status === 'failure'}
43
+ <X class={iconSize} />
44
+ {:else if status === 'running'}
45
+ <Loader2 class="{iconSize} animate-spin" />
46
+ {:else if status === 'cancelled'}
47
+ <CircleSlash class={iconSize} />
48
+ {:else if status === 'warning'}
49
+ <AlertTriangle class={iconSize} />
50
+ {:else}
51
+ <Clock class={iconSize} />
52
+ {/if}
53
+ {text}
54
+ </span>