yaxa-svelte 2.8.1 → 2.9.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/README.md +40 -28
- package/dist/components/content/Callout.svelte +182 -0
- package/dist/components/content/Callout.svelte.d.ts +39 -0
- package/dist/components/content/CodeGroup.svelte +238 -0
- package/dist/components/content/CodeGroup.svelte.d.ts +41 -0
- package/dist/components/content/Steps.svelte +146 -0
- package/dist/components/content/Steps.svelte.d.ts +39 -0
- package/dist/components/content/content.test.d.ts +1 -0
- package/dist/components/content/content.test.js +115 -0
- package/dist/components/content/index.d.ts +6 -0
- package/dist/components/content/index.js +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/root-purity.test.js +6 -0
- package/dist/server/endpoint/index.js +89 -14
- package/dist/server/endpoint/types.d.ts +21 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/observability/index.d.ts +4 -0
- package/dist/server/observability/index.js +4 -0
- package/dist/server/observability/logger.d.ts +14 -0
- package/dist/server/observability/logger.js +117 -0
- package/dist/server/observability/observability.test.d.ts +1 -0
- package/dist/server/observability/observability.test.js +159 -0
- package/dist/server/observability/timing.d.ts +6 -0
- package/dist/server/observability/timing.js +65 -0
- package/dist/server/observability/trace.d.ts +19 -0
- package/dist/server/observability/trace.js +66 -0
- package/dist/server/observability/types.d.ts +51 -0
- package/dist/server/observability/types.js +1 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ _Nuxt UI v4 & Nuxt UI Pro Equivalent for SvelteKit 2 & 3 & Svelte 5 with Built-i
|
|
|
15
15
|
[](LICENSE)
|
|
16
16
|
[](SECURITY.md)
|
|
17
17
|
[](CONTRIBUTING.md)
|
|
18
|
+
[](https://github.com/sponsors/FuntionalFrost)
|
|
18
19
|
|
|
19
20
|
<br />
|
|
20
21
|
|
|
@@ -553,39 +554,40 @@ A zero-overhead, runes-native query and mutation cache built specifically for Sv
|
|
|
553
554
|
|
|
554
555
|
## 📦 Subpath Exports Architecture
|
|
555
556
|
|
|
556
|
-
| Subpath | Description
|
|
557
|
-
| :---------------------- |
|
|
558
|
-
| `yaxa-svelte` | Core UI Primitives, SEO Components, `<Gate />`, Type-Safe Client RPC (`createApiClient`), Runes Query Engine (`createQuery`, `createMutation`, `QueryClient`), Theme System & Composables (`useGate`, `useUpload`, `useSSE`, `useToast`, `useClipboard`, etc.)
|
|
559
|
-
| `yaxa-svelte/seo` | Standalone SEO & Agent Engine: Sitemaps, robots.txt, XSL stylesheets, OpenGraph SVG, Schema.org JSON-LD (FAQPage, Product), WebManifest, llms.txt, skill.md, `useSeo()` runes, and prerendering utilities (Zero DB/Auth dependencies; 100% compatible with `@sveltejs/adapter-static`)
|
|
560
|
-
| `yaxa-svelte/admin` | Drizzle ORM Schema Introspection, `<AdminDashboard />`, `createYaxaAdminHook`, `createDrizzleAdmin`, `<DevSandbox />`, `<RecordDrawer />`
|
|
561
|
-
| `yaxa-svelte/server` | `yaxaEndpoint` Pipeline, Declarative Guards, OpenAPI 3.1 & Scalar Docs, RFC 7807, Real-Time SSE (`yaxaSSE`, `broadcast`), AI Streaming (`createAiStreamHandler`), Background Jobs (`enqueueJob`, `createJobRunner`), `eventBus`, Server Hooks, DB, Storage & Email
|
|
562
|
-
| `yaxa-svelte/auth` | Better-Auth client & server integration (`useAuth`, `<SignInCard />`, `<SignUpCard />`, `<ResetPasswordCard />`, `<AuthCard />`, `<PasskeyUI />`, `<TwoFactorModal />`, `createYaxaAuth`)
|
|
563
|
-
| `yaxa-svelte/db` | Multi-dialect Drizzle ORM clients & schemas for Neon (PostgreSQL) and Turso (SQLite/LibSQL)
|
|
564
|
-
| `yaxa-svelte/polar` | Polar.sh billing, customer portal sessions, and webhook synchronization
|
|
565
|
-
| `yaxa-svelte/email` | Resend transactional email templates with automatic development console fallback
|
|
566
|
-
| `yaxa-svelte/storage` | AWS S3 and Cloudflare R2 presigned upload URL generators
|
|
567
|
-
| `yaxa-svelte/testing` | Test mocks & utilities (`createMockSiteConfig`, `createMockUser`, `createTestWrapperProps`)
|
|
568
|
-
| `yaxa-svelte/vite` | Turnkey Vite plugin (`yaxa()`)
|
|
569
|
-
| `yaxa-svelte/yaxa.css` | Standalone turnkey Tailwind CSS v4 stylesheet with base styling, `@theme`, and scrollbars
|
|
570
|
-
| `yaxa-svelte/theme.css` | Pure Tailwind CSS v4 design tokens, `:root` variables, and harmonic radius scale without `@import 'tailwindcss'`
|
|
557
|
+
| Subpath | Description |
|
|
558
|
+
| :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
559
|
+
| `yaxa-svelte` | Core UI Primitives, SEO Components, `<Gate />`, Type-Safe Client RPC (`createApiClient`), Runes Query Engine (`createQuery`, `createMutation`, `QueryClient`), Theme System & Composables (`useGate`, `useUpload`, `useSSE`, `useToast`, `useClipboard`, etc.) |
|
|
560
|
+
| `yaxa-svelte/seo` | Standalone SEO & Agent Engine: Sitemaps, robots.txt, XSL stylesheets, OpenGraph SVG, Schema.org JSON-LD (FAQPage, Product), WebManifest, llms.txt, skill.md, `useSeo()` runes, and prerendering utilities (Zero DB/Auth dependencies; 100% compatible with `@sveltejs/adapter-static`) |
|
|
561
|
+
| `yaxa-svelte/admin` | Drizzle ORM Schema Introspection, `<AdminDashboard />`, `createYaxaAdminHook`, `createDrizzleAdmin`, `<DevSandbox />`, `<RecordDrawer />` |
|
|
562
|
+
| `yaxa-svelte/server` | `yaxaEndpoint` Pipeline, W3C Distributed Tracing (`traceparent`, `requestId`), Contextual Logger & `Server-Timing` Metrics, Declarative Guards, OpenAPI 3.1 & Scalar Docs, RFC 7807, Real-Time SSE (`yaxaSSE`, `broadcast`), AI Streaming (`createAiStreamHandler`), Background Jobs (`enqueueJob`, `createJobRunner`), `eventBus`, Server Hooks, DB, Storage & Email |
|
|
563
|
+
| `yaxa-svelte/auth` | Better-Auth client & server integration (`useAuth`, `<SignInCard />`, `<SignUpCard />`, `<ResetPasswordCard />`, `<AuthCard />`, `<PasskeyUI />`, `<TwoFactorModal />`, `createYaxaAuth`) |
|
|
564
|
+
| `yaxa-svelte/db` | Multi-dialect Drizzle ORM clients & schemas for Neon (PostgreSQL) and Turso (SQLite/LibSQL) |
|
|
565
|
+
| `yaxa-svelte/polar` | Polar.sh billing, customer portal sessions, and webhook synchronization |
|
|
566
|
+
| `yaxa-svelte/email` | Resend transactional email templates with automatic development console fallback |
|
|
567
|
+
| `yaxa-svelte/storage` | AWS S3 and Cloudflare R2 presigned upload URL generators |
|
|
568
|
+
| `yaxa-svelte/testing` | Test mocks & utilities (`createMockSiteConfig`, `createMockUser`, `createTestWrapperProps`) |
|
|
569
|
+
| `yaxa-svelte/vite` | Turnkey Vite plugin (`yaxa()`) |
|
|
570
|
+
| `yaxa-svelte/yaxa.css` | Standalone turnkey Tailwind CSS v4 stylesheet with base styling, `@theme`, and scrollbars |
|
|
571
|
+
| `yaxa-svelte/theme.css` | Pure Tailwind CSS v4 design tokens, `:root` variables, and harmonic radius scale without `@import 'tailwindcss'` |
|
|
571
572
|
|
|
572
573
|
---
|
|
573
574
|
|
|
574
575
|
## 🧩 Component Library
|
|
575
576
|
|
|
576
|
-
| Category | Components
|
|
577
|
-
| :--------------------- |
|
|
578
|
-
| **Theming & Studio** | `<ThemeStudio>`, `theme`, `ACCENT_PALETTES`, `NEUTRAL_PALETTES`, `RADIUS_PRESETS`, Harmonic Radius Multiplier Scale, Chart Palette Series Tokens
|
|
579
|
-
| **AI Primitives** | `<AiChat>`, `<PromptBar>`, `<AiThought>`, `<AiToolCall>`, `useAiChat`
|
|
580
|
-
| **Charts (Pure SVG)** | `<LineChart>`, `<BarChart>`, `<DonutChart>` (0 external charting dependencies, auto-responsive, dark/light theme aware)
|
|
581
|
-
| **SaaS & Auth Suite** | `<Gate>`, `<UserMenu>`, `<OrgSwitcher>`, `<PricingCard>`, `<PricingTable>`, `<SubscriptionCard>`, `useGate`, `useUpload` (Root); `<SignInCard>`, `<SignUpCard>`, `<ResetPasswordCard>`, `<AuthCard>`, `<PasskeyUI>`, `<TwoFactorModal>`, `useAuth` (`yaxa-svelte/auth`)
|
|
582
|
-
| **Admin Suite** | `<AdminDashboard>`, `<RecordDrawer>`, `<DevSandbox>`, `<ImpersonationBanner>`, `createYaxaAdminHook`, `createDrizzleAdmin`
|
|
583
|
-
| **
|
|
584
|
-
| **
|
|
585
|
-
| **
|
|
586
|
-
| **
|
|
587
|
-
| **
|
|
588
|
-
| **
|
|
577
|
+
| Category | Components |
|
|
578
|
+
| :--------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
579
|
+
| **Theming & Studio** | `<ThemeStudio>`, `theme`, `ACCENT_PALETTES`, `NEUTRAL_PALETTES`, `RADIUS_PRESETS`, Harmonic Radius Multiplier Scale, Chart Palette Series Tokens |
|
|
580
|
+
| **AI Primitives** | `<AiChat>`, `<PromptBar>`, `<AiThought>`, `<AiToolCall>`, `useAiChat` |
|
|
581
|
+
| **Charts (Pure SVG)** | `<LineChart>`, `<BarChart>`, `<DonutChart>` (0 external charting dependencies, auto-responsive, dark/light theme aware) |
|
|
582
|
+
| **SaaS & Auth Suite** | `<Gate>`, `<UserMenu>`, `<OrgSwitcher>`, `<PricingCard>`, `<PricingTable>`, `<SubscriptionCard>`, `useGate`, `useUpload` (Root); `<SignInCard>`, `<SignUpCard>`, `<ResetPasswordCard>`, `<AuthCard>`, `<PasskeyUI>`, `<TwoFactorModal>`, `useAuth` (`yaxa-svelte/auth`) |
|
|
583
|
+
| **Admin Suite** | `<AdminDashboard>`, `<RecordDrawer>`, `<DevSandbox>`, `<ImpersonationBanner>`, `createYaxaAdminHook`, `createDrizzleAdmin` |
|
|
584
|
+
| **Docs & Content** | `<Callout>` (`note`, `tip`, `important`, `warning`, `caution`, `danger`), `<CodeGroup>` (Synced multi-tab code blocks with `syncKey`), `<Steps>` (Numbered vertical step guides), `<CodeDiff>`, `<CodeBlock>` |
|
|
585
|
+
| **Elements & Data** | `<DataTable>` (Client & Server Modes), `Button`, `ButtonGroup`, `Badge`, `Avatar`, `AvatarGroup`, `Chip`, `Meter`, `MetricCard`, `Kbd`, `Icon`, `Spinner`, `Progress`, `Skeleton`, `Link`, `Logo`, `Tree`, `Timeline`, `Terminal`, `VirtualList`, `Carousel` |
|
|
586
|
+
| **Forms (Auto-Wired)** | `<Form>`, `<FormField>`, `Input`, `InputOTP`, `Textarea`, `Checkbox`, `Switch`, `Select`, `Combobox`, `MultiSelect`, `NumberInput`, `PhoneInput`, `CreditCardInput`, `RadioGroup`, `Slider`, `ColorPicker`, `ToggleGroup`, `DatePicker`, `DateRangePicker`, `Dropzone`, `<RichTextEditor>` |
|
|
587
|
+
| **Layout & Blocks** | `Container`, `Header`, `Footer`, `Section`, `Card`, `Divider`, `YaxaApp`, `DashboardShell` (with independent sidebar tokens), `ResizablePanels`, Page Blocks (`Hero`, `SaaS Metrics`, `FAQ`, `Testimonials`, `FeatureGrid`) |
|
|
588
|
+
| **Overlays & Nav** | `Tabs`, `Breadcrumb`, `Pagination`, `CommandPalette` (`⌘K`), `DropdownMenu`, `ContextMenu`, `Modal`, `Drawer`, `Slideover`, `Popover`, `Tooltip` (with `arrow={false}` & `unstyled`), `Alert`, `Accordion`, `NotificationCenter`, `Stepper` |
|
|
589
|
+
| **Motion & Actions** | `springTilt`, `smoothSlide`, `staggerFly`, `autosize`, `clickOutside`, `portal`, `sortableItem`, `infiniteScrollAction` |
|
|
590
|
+
| **Composables** | `useGate`, `useUpload`, `useClipboard`, `useShortcuts`, `useColorMode`, `useToast`, `useMediaQuery`, `useDebounce`, `useIdle`, `useInfiniteScroll`, `useSortable`, `useDataTable`, `useLocale` (Root); `useAuth` (`yaxa-svelte/auth`) |
|
|
589
591
|
|
|
590
592
|
---
|
|
591
593
|
|
|
@@ -615,6 +617,16 @@ Contributions are always welcome! Please read our [Contributing Guidelines](CONT
|
|
|
615
617
|
|
|
616
618
|
---
|
|
617
619
|
|
|
620
|
+
## 💖 Sponsors & Backing
|
|
621
|
+
|
|
622
|
+
If you find Yaxa helpful for your applications or business, consider supporting its continuous development and maintenance:
|
|
623
|
+
|
|
624
|
+
[](https://github.com/sponsors/FuntionalFrost)
|
|
625
|
+
|
|
626
|
+
- **[GitHub Sponsors](https://github.com/sponsors/FuntionalFrost)**: Fund ongoing feature additions, bug fixes, SvelteKit version tracking, and documentation improvements.
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
618
630
|
## 📄 License
|
|
619
631
|
|
|
620
632
|
[MIT License](LICENSE) © 2026 Yaxa Contributors.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import { tv, cn, type VariantProps } from '../../utils/cn.js';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { IconSource } from '../elements/Icon.svelte';
|
|
5
|
+
|
|
6
|
+
export const calloutVariants = tv({
|
|
7
|
+
base: 'not-prose group relative my-4 flex w-full gap-3.5 overflow-hidden rounded-xl border border-l-4 p-4 text-sm transition-all duration-150',
|
|
8
|
+
variants: {
|
|
9
|
+
type: {
|
|
10
|
+
note: 'border-sky-200/80 border-l-sky-500 bg-sky-50/50 text-sky-950 dark:border-sky-900/40 dark:border-l-sky-400 dark:bg-sky-950/25 dark:text-sky-100',
|
|
11
|
+
info: 'border-sky-200/80 border-l-sky-500 bg-sky-50/50 text-sky-950 dark:border-sky-900/40 dark:border-l-sky-400 dark:bg-sky-950/25 dark:text-sky-100',
|
|
12
|
+
tip: 'border-emerald-200/80 border-l-emerald-500 bg-emerald-50/50 text-emerald-950 dark:border-emerald-900/40 dark:border-l-emerald-400 dark:bg-emerald-950/25 dark:text-emerald-100',
|
|
13
|
+
success:
|
|
14
|
+
'border-emerald-200/80 border-l-emerald-500 bg-emerald-50/50 text-emerald-950 dark:border-emerald-900/40 dark:border-l-emerald-400 dark:bg-emerald-950/25 dark:text-emerald-100',
|
|
15
|
+
important:
|
|
16
|
+
'border-violet-200/80 border-l-violet-500 bg-violet-50/50 text-violet-950 dark:border-violet-900/40 dark:border-l-violet-400 dark:bg-violet-950/25 dark:text-violet-100',
|
|
17
|
+
warning:
|
|
18
|
+
'border-amber-200/80 border-l-amber-500 bg-amber-50/50 text-amber-950 dark:border-amber-900/40 dark:border-l-amber-400 dark:bg-amber-950/25 dark:text-amber-100',
|
|
19
|
+
caution:
|
|
20
|
+
'border-rose-200/80 border-l-rose-500 bg-rose-50/50 text-rose-950 dark:border-rose-900/40 dark:border-l-rose-400 dark:bg-rose-950/25 dark:text-rose-100',
|
|
21
|
+
danger:
|
|
22
|
+
'border-rose-200/80 border-l-rose-500 bg-rose-50/50 text-rose-950 dark:border-rose-900/40 dark:border-l-rose-400 dark:bg-rose-950/25 dark:text-rose-100'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
type: 'note'
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export type CalloutType = NonNullable<VariantProps<typeof calloutVariants>['type']>;
|
|
31
|
+
|
|
32
|
+
export interface CalloutSlots {
|
|
33
|
+
root?: string;
|
|
34
|
+
icon?: string;
|
|
35
|
+
content?: string;
|
|
36
|
+
title?: string;
|
|
37
|
+
body?: string;
|
|
38
|
+
close?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type CalloutProps = VariantProps<typeof calloutVariants> & {
|
|
42
|
+
title?: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
icon?: IconSource;
|
|
45
|
+
href?: string;
|
|
46
|
+
target?: string;
|
|
47
|
+
closable?: boolean;
|
|
48
|
+
onclose?: () => void;
|
|
49
|
+
class?: string;
|
|
50
|
+
ui?: CalloutSlots;
|
|
51
|
+
children?: Snippet;
|
|
52
|
+
};
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<script lang="ts">
|
|
56
|
+
import Icon from '../elements/Icon.svelte';
|
|
57
|
+
|
|
58
|
+
let {
|
|
59
|
+
type = 'note',
|
|
60
|
+
title,
|
|
61
|
+
description,
|
|
62
|
+
icon,
|
|
63
|
+
href,
|
|
64
|
+
target,
|
|
65
|
+
closable = false,
|
|
66
|
+
onclose,
|
|
67
|
+
class: className = '',
|
|
68
|
+
ui,
|
|
69
|
+
children
|
|
70
|
+
}: CalloutProps = $props();
|
|
71
|
+
|
|
72
|
+
let visible = $state(true);
|
|
73
|
+
|
|
74
|
+
const iconColorMap: Record<CalloutType, string> = {
|
|
75
|
+
note: 'text-sky-600 dark:text-sky-400',
|
|
76
|
+
info: 'text-sky-600 dark:text-sky-400',
|
|
77
|
+
tip: 'text-emerald-600 dark:text-emerald-400',
|
|
78
|
+
success: 'text-emerald-600 dark:text-emerald-400',
|
|
79
|
+
important: 'text-violet-600 dark:text-violet-400',
|
|
80
|
+
warning: 'text-amber-600 dark:text-amber-400',
|
|
81
|
+
caution: 'text-rose-600 dark:text-rose-400',
|
|
82
|
+
danger: 'text-rose-600 dark:text-rose-400'
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let resolvedIcon = $derived.by((): IconSource => {
|
|
86
|
+
if (icon) return icon;
|
|
87
|
+
switch (type) {
|
|
88
|
+
case 'tip':
|
|
89
|
+
case 'success':
|
|
90
|
+
return 'check';
|
|
91
|
+
case 'important':
|
|
92
|
+
return 'sparkles';
|
|
93
|
+
case 'warning':
|
|
94
|
+
case 'caution':
|
|
95
|
+
case 'danger':
|
|
96
|
+
return 'alert';
|
|
97
|
+
case 'note':
|
|
98
|
+
case 'info':
|
|
99
|
+
default:
|
|
100
|
+
return 'info';
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function handleClose(e: MouseEvent) {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
e.stopPropagation();
|
|
107
|
+
visible = false;
|
|
108
|
+
onclose?.();
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
{#if visible}
|
|
113
|
+
<svelte:element
|
|
114
|
+
this={href ? 'a' : 'div'}
|
|
115
|
+
{href}
|
|
116
|
+
{target}
|
|
117
|
+
rel={target === '_blank' ? 'noopener noreferrer' : undefined}
|
|
118
|
+
data-slot="root"
|
|
119
|
+
data-callout-type={type}
|
|
120
|
+
role="note"
|
|
121
|
+
class={cn(
|
|
122
|
+
calloutVariants({ type, class: className }),
|
|
123
|
+
href && 'cursor-pointer hover:shadow-sm',
|
|
124
|
+
ui?.root
|
|
125
|
+
)}
|
|
126
|
+
>
|
|
127
|
+
<div data-slot="icon" class={cn('mt-0.5 shrink-0', iconColorMap[type ?? 'note'], ui?.icon)}>
|
|
128
|
+
<Icon name={resolvedIcon} size="sm" />
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div data-slot="content" class={cn('min-w-0 flex-1 space-y-1', ui?.content)}>
|
|
132
|
+
{#if title}
|
|
133
|
+
<div
|
|
134
|
+
data-slot="title"
|
|
135
|
+
class={cn('flex items-center gap-1.5 font-semibold tracking-tight', ui?.title)}
|
|
136
|
+
>
|
|
137
|
+
<span>{title}</span>
|
|
138
|
+
{#if href}
|
|
139
|
+
<Icon
|
|
140
|
+
name="arrow-right"
|
|
141
|
+
size="xs"
|
|
142
|
+
class="transition-transform duration-150 group-hover:translate-x-0.5"
|
|
143
|
+
/>
|
|
144
|
+
{/if}
|
|
145
|
+
</div>
|
|
146
|
+
{/if}
|
|
147
|
+
|
|
148
|
+
{#if description}
|
|
149
|
+
<div data-slot="body" class={cn('leading-relaxed opacity-90', ui?.body)}>
|
|
150
|
+
{description}
|
|
151
|
+
</div>
|
|
152
|
+
{/if}
|
|
153
|
+
|
|
154
|
+
{#if children}
|
|
155
|
+
<div
|
|
156
|
+
data-slot="body"
|
|
157
|
+
class={cn(
|
|
158
|
+
'leading-relaxed opacity-90 [&_code]:rounded [&_code]:bg-black/5 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs dark:[&_code]:bg-white/10',
|
|
159
|
+
ui?.body
|
|
160
|
+
)}
|
|
161
|
+
>
|
|
162
|
+
{@render children()}
|
|
163
|
+
</div>
|
|
164
|
+
{/if}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{#if closable}
|
|
168
|
+
<button
|
|
169
|
+
type="button"
|
|
170
|
+
data-slot="close"
|
|
171
|
+
onclick={handleClose}
|
|
172
|
+
class={cn(
|
|
173
|
+
'-mt-1 -mr-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-lg opacity-60 transition-opacity hover:bg-black/5 hover:opacity-100 focus:outline-none dark:hover:bg-white/10',
|
|
174
|
+
ui?.close
|
|
175
|
+
)}
|
|
176
|
+
aria-label="Dismiss callout"
|
|
177
|
+
>
|
|
178
|
+
<Icon name="close" size="xs" />
|
|
179
|
+
</button>
|
|
180
|
+
{/if}
|
|
181
|
+
</svelte:element>
|
|
182
|
+
{/if}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type VariantProps } from '../../utils/cn.js';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { IconSource } from '../elements/Icon.svelte';
|
|
4
|
+
export declare const calloutVariants: import("tailwind-variants").TVReturnType<{
|
|
5
|
+
type: {
|
|
6
|
+
note: string;
|
|
7
|
+
info: string;
|
|
8
|
+
tip: string;
|
|
9
|
+
success: string;
|
|
10
|
+
important: string;
|
|
11
|
+
warning: string;
|
|
12
|
+
caution: string;
|
|
13
|
+
danger: string;
|
|
14
|
+
};
|
|
15
|
+
}, undefined, "not-prose group relative my-4 flex w-full gap-3.5 overflow-hidden rounded-xl border border-l-4 p-4 text-sm transition-all duration-150", undefined, undefined, undefined>;
|
|
16
|
+
export type CalloutType = NonNullable<VariantProps<typeof calloutVariants>['type']>;
|
|
17
|
+
export interface CalloutSlots {
|
|
18
|
+
root?: string;
|
|
19
|
+
icon?: string;
|
|
20
|
+
content?: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
body?: string;
|
|
23
|
+
close?: string;
|
|
24
|
+
}
|
|
25
|
+
export type CalloutProps = VariantProps<typeof calloutVariants> & {
|
|
26
|
+
title?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
icon?: IconSource;
|
|
29
|
+
href?: string;
|
|
30
|
+
target?: string;
|
|
31
|
+
closable?: boolean;
|
|
32
|
+
onclose?: () => void;
|
|
33
|
+
class?: string;
|
|
34
|
+
ui?: CalloutSlots;
|
|
35
|
+
children?: Snippet;
|
|
36
|
+
};
|
|
37
|
+
declare const Callout: import("svelte").Component<CalloutProps, {}, "">;
|
|
38
|
+
type Callout = ReturnType<typeof Callout>;
|
|
39
|
+
export default Callout;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import { tv, cn, type VariantProps } from '../../utils/cn.js';
|
|
3
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
import type { IconSource } from '../elements/Icon.svelte';
|
|
6
|
+
|
|
7
|
+
// Reactive cross-instance tab synchronization registry
|
|
8
|
+
const syncedTabs = new SvelteMap<string, string>();
|
|
9
|
+
|
|
10
|
+
export const codeGroupVariants = tv({
|
|
11
|
+
base: 'not-prose my-4 overflow-hidden rounded-xl border border-neutral-800 bg-neutral-950 shadow-sm',
|
|
12
|
+
variants: {
|
|
13
|
+
themeMode: {
|
|
14
|
+
dark: 'border-neutral-800 bg-neutral-950 text-neutral-100',
|
|
15
|
+
adaptive:
|
|
16
|
+
'border-neutral-200 bg-neutral-50 text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-100'
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
themeMode: 'dark'
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export interface CodeGroupItem {
|
|
25
|
+
label: string;
|
|
26
|
+
value?: string;
|
|
27
|
+
code: string;
|
|
28
|
+
language?: string;
|
|
29
|
+
filename?: string;
|
|
30
|
+
icon?: IconSource;
|
|
31
|
+
showLineNumbers?: boolean;
|
|
32
|
+
highlightLines?: number[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CodeGroupSlots {
|
|
36
|
+
root?: string;
|
|
37
|
+
header?: string;
|
|
38
|
+
tabList?: string;
|
|
39
|
+
tab?: string;
|
|
40
|
+
activeTab?: string;
|
|
41
|
+
copyButton?: string;
|
|
42
|
+
body?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type CodeGroupProps = VariantProps<typeof codeGroupVariants> & {
|
|
46
|
+
items?: CodeGroupItem[];
|
|
47
|
+
activeTab?: string;
|
|
48
|
+
syncKey?: string;
|
|
49
|
+
showLineNumbers?: boolean;
|
|
50
|
+
wrap?: boolean;
|
|
51
|
+
class?: string;
|
|
52
|
+
ui?: CodeGroupSlots;
|
|
53
|
+
children?: Snippet<[string]>;
|
|
54
|
+
};
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<script lang="ts">
|
|
58
|
+
import CodeBlock from '../elements/CodeBlock.svelte';
|
|
59
|
+
import Icon from '../elements/Icon.svelte';
|
|
60
|
+
|
|
61
|
+
let {
|
|
62
|
+
items = [],
|
|
63
|
+
activeTab = $bindable(''),
|
|
64
|
+
syncKey,
|
|
65
|
+
showLineNumbers = false,
|
|
66
|
+
wrap = false,
|
|
67
|
+
themeMode = 'dark',
|
|
68
|
+
class: className = '',
|
|
69
|
+
ui,
|
|
70
|
+
children
|
|
71
|
+
}: CodeGroupProps = $props();
|
|
72
|
+
|
|
73
|
+
let copied = $state(false);
|
|
74
|
+
let copyTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
75
|
+
|
|
76
|
+
function getItemValue(item: CodeGroupItem): string {
|
|
77
|
+
return item.value ?? item.label;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function inferLanguage(item: CodeGroupItem): string {
|
|
81
|
+
if (item.language) return item.language;
|
|
82
|
+
const lower = (item.filename || item.label).toLowerCase();
|
|
83
|
+
if (['pnpm', 'npm', 'bun', 'yarn', 'npx', 'bash', 'sh', 'terminal', 'shell'].includes(lower)) {
|
|
84
|
+
return 'bash';
|
|
85
|
+
}
|
|
86
|
+
if (lower.endsWith('.svelte')) return 'svelte';
|
|
87
|
+
if (lower.endsWith('.ts') || lower.endsWith('.mts')) return 'typescript';
|
|
88
|
+
if (lower.endsWith('.js') || lower.endsWith('.mjs')) return 'javascript';
|
|
89
|
+
if (lower.endsWith('.json')) return 'json';
|
|
90
|
+
if (lower.endsWith('.css')) return 'css';
|
|
91
|
+
if (lower.endsWith('.yml') || lower.endsWith('.yaml')) return 'yaml';
|
|
92
|
+
if (lower.endsWith('.md') || lower.endsWith('.svx')) return 'markdown';
|
|
93
|
+
if (lower.endsWith('.sql')) return 'sql';
|
|
94
|
+
return 'typescript';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let resolvedTab = $derived.by(() => {
|
|
98
|
+
if (syncKey) {
|
|
99
|
+
const memVal = syncedTabs.get(syncKey);
|
|
100
|
+
if (memVal && items.some((item) => getItemValue(item) === memVal)) {
|
|
101
|
+
return memVal;
|
|
102
|
+
}
|
|
103
|
+
if (typeof window !== 'undefined') {
|
|
104
|
+
try {
|
|
105
|
+
const stored = window.localStorage.getItem(`yaxa-cg:${syncKey}`);
|
|
106
|
+
if (stored && items.some((item) => getItemValue(item) === stored)) {
|
|
107
|
+
return stored;
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// Ignore storage access errors
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (activeTab && items.some((item) => getItemValue(item) === activeTab)) {
|
|
115
|
+
return activeTab;
|
|
116
|
+
}
|
|
117
|
+
return items.length > 0 ? getItemValue(items[0]) : '';
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
let currentItem = $derived(
|
|
121
|
+
items.find((item) => getItemValue(item) === resolvedTab) ?? items[0] ?? null
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
function selectTab(val: string) {
|
|
125
|
+
activeTab = val;
|
|
126
|
+
if (syncKey) {
|
|
127
|
+
syncedTabs.set(syncKey, val);
|
|
128
|
+
if (typeof window !== 'undefined') {
|
|
129
|
+
try {
|
|
130
|
+
window.localStorage.setItem(`yaxa-cg:${syncKey}`, val);
|
|
131
|
+
} catch {
|
|
132
|
+
// Ignore storage write errors
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function handleCopy() {
|
|
139
|
+
if (!currentItem?.code) return;
|
|
140
|
+
try {
|
|
141
|
+
await navigator.clipboard.writeText(currentItem.code);
|
|
142
|
+
copied = true;
|
|
143
|
+
if (copyTimeout) clearTimeout(copyTimeout);
|
|
144
|
+
copyTimeout = setTimeout(() => {
|
|
145
|
+
copied = false;
|
|
146
|
+
}, 2000);
|
|
147
|
+
} catch {
|
|
148
|
+
// Clipboard API unavailable
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
</script>
|
|
152
|
+
|
|
153
|
+
<div data-slot="root" class={cn(codeGroupVariants({ themeMode, class: className }), ui?.root)}>
|
|
154
|
+
<!-- Tab Bar Header -->
|
|
155
|
+
<div
|
|
156
|
+
data-slot="header"
|
|
157
|
+
class={cn(
|
|
158
|
+
'flex items-center justify-between border-b px-3 py-1.5',
|
|
159
|
+
themeMode === 'adaptive'
|
|
160
|
+
? 'border-neutral-200 bg-neutral-100/80 dark:border-neutral-800 dark:bg-neutral-900/80'
|
|
161
|
+
: 'border-neutral-800 bg-neutral-900/80',
|
|
162
|
+
ui?.header
|
|
163
|
+
)}
|
|
164
|
+
>
|
|
165
|
+
<div
|
|
166
|
+
data-slot="tabList"
|
|
167
|
+
role="tablist"
|
|
168
|
+
aria-label="Code group tabs"
|
|
169
|
+
class={cn('flex items-center gap-1 overflow-x-auto', ui?.tabList)}
|
|
170
|
+
>
|
|
171
|
+
{#each items as item (item.value ?? item.label)}
|
|
172
|
+
{@const val = getItemValue(item)}
|
|
173
|
+
{@const isActive = resolvedTab === val}
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
role="tab"
|
|
177
|
+
aria-selected={isActive}
|
|
178
|
+
data-slot="tab"
|
|
179
|
+
data-state={isActive ? 'active' : 'inactive'}
|
|
180
|
+
onclick={() => selectTab(val)}
|
|
181
|
+
class={cn(
|
|
182
|
+
'relative inline-flex cursor-pointer items-center gap-1.5 rounded-lg px-2.5 py-1 font-mono text-xs font-medium whitespace-nowrap transition-colors focus:outline-none',
|
|
183
|
+
themeMode === 'adaptive'
|
|
184
|
+
? isActive
|
|
185
|
+
? 'bg-white text-neutral-950 shadow-2xs dark:bg-neutral-800 dark:text-white'
|
|
186
|
+
: 'text-neutral-500 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200'
|
|
187
|
+
: isActive
|
|
188
|
+
? 'bg-neutral-800 text-white shadow-2xs'
|
|
189
|
+
: 'text-neutral-400 hover:bg-neutral-800/50 hover:text-neutral-200',
|
|
190
|
+
ui?.tab,
|
|
191
|
+
isActive && ui?.activeTab
|
|
192
|
+
)}
|
|
193
|
+
>
|
|
194
|
+
{#if item.icon}
|
|
195
|
+
<Icon name={item.icon} size="xs" />
|
|
196
|
+
{/if}
|
|
197
|
+
<span>{item.label}</span>
|
|
198
|
+
</button>
|
|
199
|
+
{/each}
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
{#if currentItem}
|
|
203
|
+
<button
|
|
204
|
+
type="button"
|
|
205
|
+
data-slot="copyButton"
|
|
206
|
+
onclick={handleCopy}
|
|
207
|
+
class={cn(
|
|
208
|
+
'ml-2 inline-flex shrink-0 cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors',
|
|
209
|
+
themeMode === 'adaptive'
|
|
210
|
+
? 'text-neutral-500 hover:bg-neutral-200/70 hover:text-neutral-900 dark:text-neutral-400 dark:hover:bg-neutral-800 dark:hover:text-white'
|
|
211
|
+
: 'text-neutral-400 hover:bg-neutral-800 hover:text-white',
|
|
212
|
+
ui?.copyButton
|
|
213
|
+
)}
|
|
214
|
+
aria-label="Copy code"
|
|
215
|
+
>
|
|
216
|
+
<Icon name={copied ? 'check' : 'copy'} size="xs" />
|
|
217
|
+
<span>{copied ? 'Copied' : 'Copy'}</span>
|
|
218
|
+
</button>
|
|
219
|
+
{/if}
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<!-- Active Code Body -->
|
|
223
|
+
<div data-slot="body" class={cn('[&>div]:my-0 [&>div]:rounded-none [&>div]:border-0', ui?.body)}>
|
|
224
|
+
{#if children}
|
|
225
|
+
{@render children(resolvedTab)}
|
|
226
|
+
{:else if currentItem}
|
|
227
|
+
<CodeBlock
|
|
228
|
+
code={currentItem.code}
|
|
229
|
+
language={inferLanguage(currentItem)}
|
|
230
|
+
showLineNumbers={currentItem.showLineNumbers ?? showLineNumbers}
|
|
231
|
+
highlightLines={currentItem.highlightLines}
|
|
232
|
+
{wrap}
|
|
233
|
+
{themeMode}
|
|
234
|
+
class="my-0 rounded-none border-0 shadow-none"
|
|
235
|
+
/>
|
|
236
|
+
{/if}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type VariantProps } from '../../utils/cn.js';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { IconSource } from '../elements/Icon.svelte';
|
|
4
|
+
export declare const codeGroupVariants: import("tailwind-variants").TVReturnType<{
|
|
5
|
+
themeMode: {
|
|
6
|
+
dark: string;
|
|
7
|
+
adaptive: string;
|
|
8
|
+
};
|
|
9
|
+
}, undefined, "not-prose my-4 overflow-hidden rounded-xl border border-neutral-800 bg-neutral-950 shadow-sm", undefined, undefined, undefined>;
|
|
10
|
+
export interface CodeGroupItem {
|
|
11
|
+
label: string;
|
|
12
|
+
value?: string;
|
|
13
|
+
code: string;
|
|
14
|
+
language?: string;
|
|
15
|
+
filename?: string;
|
|
16
|
+
icon?: IconSource;
|
|
17
|
+
showLineNumbers?: boolean;
|
|
18
|
+
highlightLines?: number[];
|
|
19
|
+
}
|
|
20
|
+
export interface CodeGroupSlots {
|
|
21
|
+
root?: string;
|
|
22
|
+
header?: string;
|
|
23
|
+
tabList?: string;
|
|
24
|
+
tab?: string;
|
|
25
|
+
activeTab?: string;
|
|
26
|
+
copyButton?: string;
|
|
27
|
+
body?: string;
|
|
28
|
+
}
|
|
29
|
+
export type CodeGroupProps = VariantProps<typeof codeGroupVariants> & {
|
|
30
|
+
items?: CodeGroupItem[];
|
|
31
|
+
activeTab?: string;
|
|
32
|
+
syncKey?: string;
|
|
33
|
+
showLineNumbers?: boolean;
|
|
34
|
+
wrap?: boolean;
|
|
35
|
+
class?: string;
|
|
36
|
+
ui?: CodeGroupSlots;
|
|
37
|
+
children?: Snippet<[string]>;
|
|
38
|
+
};
|
|
39
|
+
declare const CodeGroup: import("svelte").Component<CodeGroupProps, {}, "activeTab">;
|
|
40
|
+
type CodeGroup = ReturnType<typeof CodeGroup>;
|
|
41
|
+
export default CodeGroup;
|