@d34dman/flowdrop 0.0.1

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 (46) hide show
  1. package/README.md +293 -0
  2. package/dist/adapters/WorkflowAdapter.d.ts +166 -0
  3. package/dist/adapters/WorkflowAdapter.js +337 -0
  4. package/dist/api/client.d.ts +79 -0
  5. package/dist/api/client.js +208 -0
  6. package/dist/app.css +0 -0
  7. package/dist/clients/ApiClient.d.ts +203 -0
  8. package/dist/clients/ApiClient.js +212 -0
  9. package/dist/components/App.svelte +237 -0
  10. package/dist/components/App.svelte.d.ts +3 -0
  11. package/dist/components/CanvasBanner.svelte +51 -0
  12. package/dist/components/CanvasBanner.svelte.d.ts +22 -0
  13. package/dist/components/LoadingSpinner.svelte +36 -0
  14. package/dist/components/LoadingSpinner.svelte.d.ts +8 -0
  15. package/dist/components/Node.svelte +38 -0
  16. package/dist/components/Node.svelte.d.ts +4 -0
  17. package/dist/components/NodeSidebar.svelte +500 -0
  18. package/dist/components/NodeSidebar.svelte.d.ts +9 -0
  19. package/dist/components/WorkflowEditor.svelte +542 -0
  20. package/dist/components/WorkflowEditor.svelte.d.ts +10 -0
  21. package/dist/components/WorkflowNode.svelte +558 -0
  22. package/dist/components/WorkflowNode.svelte.d.ts +11 -0
  23. package/dist/data/samples.d.ts +17 -0
  24. package/dist/data/samples.js +1193 -0
  25. package/dist/examples/adapter-usage.d.ts +66 -0
  26. package/dist/examples/adapter-usage.js +138 -0
  27. package/dist/examples/api-client-usage.d.ts +31 -0
  28. package/dist/examples/api-client-usage.js +241 -0
  29. package/dist/index.d.ts +19 -0
  30. package/dist/index.js +27 -0
  31. package/dist/services/api.d.ts +110 -0
  32. package/dist/services/api.js +149 -0
  33. package/dist/services/workflowStorage.d.ts +37 -0
  34. package/dist/services/workflowStorage.js +116 -0
  35. package/dist/styles/base.css +858 -0
  36. package/dist/svelte-app.d.ts +17 -0
  37. package/dist/svelte-app.js +30 -0
  38. package/dist/types/index.d.ts +179 -0
  39. package/dist/types/index.js +4 -0
  40. package/dist/utils/colors.d.ts +121 -0
  41. package/dist/utils/colors.js +240 -0
  42. package/dist/utils/connections.d.ts +47 -0
  43. package/dist/utils/connections.js +240 -0
  44. package/dist/utils/icons.d.ts +102 -0
  45. package/dist/utils/icons.js +149 -0
  46. package/package.json +99 -0
@@ -0,0 +1,203 @@
1
+ /**
2
+ * FlowDrop API Client
3
+ * Type-safe client for the FlowDrop API
4
+ */
5
+ import type { NodeMetadata, Workflow, ApiResponse, NodesResponse, WorkflowResponse, WorkflowsResponse } from "../types/index.js";
6
+ /**
7
+ * API Client Configuration
8
+ */
9
+ export interface ApiClientConfig {
10
+ baseUrl: string;
11
+ apiKey?: string;
12
+ timeout?: number;
13
+ headers?: Record<string, string>;
14
+ }
15
+ /**
16
+ * Request options for API calls
17
+ */
18
+ export interface RequestOptions {
19
+ headers?: Record<string, string>;
20
+ timeout?: number;
21
+ }
22
+ /**
23
+ * Pagination parameters
24
+ */
25
+ export interface PaginationParams {
26
+ limit?: number;
27
+ offset?: number;
28
+ }
29
+ /**
30
+ * Node types query parameters
31
+ */
32
+ export interface NodeTypesQuery {
33
+ category?: string;
34
+ search?: string;
35
+ limit?: number;
36
+ offset?: number;
37
+ }
38
+ /**
39
+ * Workflows query parameters
40
+ */
41
+ export interface WorkflowsQuery {
42
+ search?: string;
43
+ tags?: string;
44
+ limit?: number;
45
+ offset?: number;
46
+ sort?: "created_at" | "updated_at" | "name";
47
+ order?: "asc" | "desc";
48
+ }
49
+ /**
50
+ * Create workflow request
51
+ */
52
+ export interface CreateWorkflowRequest {
53
+ name: string;
54
+ description?: string;
55
+ nodes?: any[];
56
+ edges?: any[];
57
+ tags?: string[];
58
+ }
59
+ /**
60
+ * Update workflow request
61
+ */
62
+ export interface UpdateWorkflowRequest {
63
+ name?: string;
64
+ description?: string;
65
+ nodes?: any[];
66
+ edges?: any[];
67
+ tags?: string[];
68
+ }
69
+ /**
70
+ * Execute workflow request
71
+ */
72
+ export interface ExecuteWorkflowRequest {
73
+ inputs: Record<string, unknown>;
74
+ options?: {
75
+ timeout?: number;
76
+ maxSteps?: number;
77
+ };
78
+ }
79
+ /**
80
+ * FlowDrop API Client Class
81
+ */
82
+ export declare class ApiClient {
83
+ private config;
84
+ private defaultHeaders;
85
+ constructor(config: ApiClientConfig);
86
+ /**
87
+ * Make an HTTP request
88
+ */
89
+ private request;
90
+ /**
91
+ * Check API health
92
+ */
93
+ healthCheck(): Promise<{
94
+ status: string;
95
+ timestamp: string;
96
+ version: string;
97
+ }>;
98
+ /**
99
+ * Get all node types
100
+ */
101
+ getNodeTypes(query?: NodeTypesQuery): Promise<NodesResponse>;
102
+ /**
103
+ * Get node type by ID
104
+ */
105
+ getNodeType(id: string): Promise<NodeTypeResponse>;
106
+ /**
107
+ * Get all workflows
108
+ */
109
+ getWorkflows(query?: WorkflowsQuery): Promise<WorkflowsResponse>;
110
+ /**
111
+ * Get workflow by ID
112
+ */
113
+ getWorkflow(id: string): Promise<WorkflowResponse>;
114
+ /**
115
+ * Create a new workflow
116
+ */
117
+ createWorkflow(data: CreateWorkflowRequest): Promise<WorkflowResponse>;
118
+ /**
119
+ * Update workflow
120
+ */
121
+ updateWorkflow(id: string, data: UpdateWorkflowRequest): Promise<WorkflowResponse>;
122
+ /**
123
+ * Delete workflow
124
+ */
125
+ deleteWorkflow(id: string): Promise<void>;
126
+ /**
127
+ * Execute workflow
128
+ */
129
+ executeWorkflow(id: string, data: ExecuteWorkflowRequest): Promise<ExecutionResponse>;
130
+ /**
131
+ * Get execution status
132
+ */
133
+ getExecutionStatus(id: string): Promise<ExecutionStatusResponse>;
134
+ /**
135
+ * Cancel execution
136
+ */
137
+ cancelExecution(id: string): Promise<ExecutionResponse>;
138
+ /**
139
+ * Export workflow
140
+ */
141
+ exportWorkflow(id: string, format?: "json" | "yaml"): Promise<Workflow>;
142
+ /**
143
+ * Import workflow
144
+ */
145
+ importWorkflow(workflow: Workflow): Promise<WorkflowResponse>;
146
+ /**
147
+ * Validate workflow
148
+ */
149
+ validateWorkflow(workflow: Workflow): Promise<ValidationResponse>;
150
+ /**
151
+ * Wait for execution completion
152
+ */
153
+ waitForExecution(id: string, pollInterval?: number): Promise<ExecutionStatusResponse>;
154
+ /**
155
+ * Get workflows by tag
156
+ */
157
+ getWorkflowsByTag(tag: string, query?: Omit<WorkflowsQuery, "tags">): Promise<WorkflowsResponse>;
158
+ /**
159
+ * Get node types by category
160
+ */
161
+ getNodeTypesByCategory(category: string, query?: Omit<NodeTypesQuery, "category">): Promise<NodesResponse>;
162
+ }
163
+ /**
164
+ * API Error Class
165
+ */
166
+ export declare class ApiError extends Error {
167
+ status: number;
168
+ code?: string;
169
+ details?: unknown;
170
+ constructor(status: number, message: string, code?: string, details?: unknown);
171
+ }
172
+ /**
173
+ * Execution response types
174
+ */
175
+ export interface ExecutionResponse extends ApiResponse<{
176
+ executionId: string;
177
+ status: "pending" | "running" | "completed" | "failed" | "cancelled";
178
+ message: string;
179
+ }> {
180
+ }
181
+ export interface ExecutionStatusResponse extends ApiResponse<{
182
+ executionId: string;
183
+ status: "pending" | "running" | "completed" | "failed" | "cancelled";
184
+ result?: unknown;
185
+ error?: string;
186
+ startTime: string;
187
+ endTime?: string;
188
+ duration?: number;
189
+ nodeResults?: Record<string, unknown>;
190
+ }> {
191
+ }
192
+ export interface ValidationResponse extends ApiResponse<{
193
+ valid: boolean;
194
+ errors: string[];
195
+ warnings: string[];
196
+ suggestions?: string[];
197
+ }> {
198
+ }
199
+ /**
200
+ * Type for node type response
201
+ */
202
+ export interface NodeTypeResponse extends ApiResponse<NodeMetadata> {
203
+ }
@@ -0,0 +1,212 @@
1
+ /**
2
+ * FlowDrop API Client
3
+ * Type-safe client for the FlowDrop API
4
+ */
5
+ /**
6
+ * FlowDrop API Client Class
7
+ */
8
+ export class ApiClient {
9
+ config;
10
+ defaultHeaders;
11
+ constructor(config) {
12
+ this.config = {
13
+ timeout: 30000,
14
+ ...config
15
+ };
16
+ this.defaultHeaders = {
17
+ "Content-Type": "application/json",
18
+ ...config.headers
19
+ };
20
+ if (config.apiKey) {
21
+ this.defaultHeaders.Authorization = `Bearer ${config.apiKey}`;
22
+ }
23
+ }
24
+ /**
25
+ * Make an HTTP request
26
+ */
27
+ async request(method, path, data, options = {}) {
28
+ const url = `${this.config.baseUrl}${path}`;
29
+ const headers = { ...this.defaultHeaders, ...options.headers };
30
+ const timeout = options.timeout || this.config.timeout;
31
+ const controller = new AbortController();
32
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
33
+ try {
34
+ const response = await fetch(url, {
35
+ method,
36
+ headers,
37
+ body: data ? JSON.stringify(data) : undefined,
38
+ signal: controller.signal
39
+ });
40
+ clearTimeout(timeoutId);
41
+ if (!response.ok) {
42
+ const errorData = await response.json().catch(() => ({}));
43
+ throw new ApiError(response.status, errorData.error || `HTTP ${response.status}`, errorData.code, errorData.details);
44
+ }
45
+ return await response.json();
46
+ }
47
+ catch (error) {
48
+ clearTimeout(timeoutId);
49
+ if (error instanceof ApiError) {
50
+ throw error;
51
+ }
52
+ if (error instanceof Error && error.name === "AbortError") {
53
+ throw new ApiError(408, "Request timeout", "TIMEOUT");
54
+ }
55
+ throw new ApiError(500, error instanceof Error ? error.message : "Network error", "NETWORK_ERROR");
56
+ }
57
+ }
58
+ // ===== HEALTH CHECK =====
59
+ /**
60
+ * Check API health
61
+ */
62
+ async healthCheck() {
63
+ return this.request("GET", "/health");
64
+ }
65
+ // ===== NODE TYPES =====
66
+ /**
67
+ * Get all node types
68
+ */
69
+ async getNodeTypes(query) {
70
+ const params = new URLSearchParams();
71
+ if (query?.category)
72
+ params.append("category", query.category);
73
+ if (query?.search)
74
+ params.append("search", query.search);
75
+ if (query?.limit)
76
+ params.append("limit", query.limit.toString());
77
+ if (query?.offset)
78
+ params.append("offset", query.offset.toString());
79
+ const path = `/nodes${params.toString() ? `?${params.toString()}` : ""}`;
80
+ return this.request("GET", path);
81
+ }
82
+ /**
83
+ * Get node type by ID
84
+ */
85
+ async getNodeType(id) {
86
+ return this.request("GET", `/nodes/${id}`);
87
+ }
88
+ // ===== WORKFLOWS =====
89
+ /**
90
+ * Get all workflows
91
+ */
92
+ async getWorkflows(query) {
93
+ const params = new URLSearchParams();
94
+ if (query?.search)
95
+ params.append("search", query.search);
96
+ if (query?.tags)
97
+ params.append("tags", query.tags);
98
+ if (query?.limit)
99
+ params.append("limit", query.limit.toString());
100
+ if (query?.offset)
101
+ params.append("offset", query.offset.toString());
102
+ if (query?.sort)
103
+ params.append("sort", query.sort);
104
+ if (query?.order)
105
+ params.append("order", query.order);
106
+ const path = `/workflows${params.toString() ? `?${params.toString()}` : ""}`;
107
+ return this.request("GET", path);
108
+ }
109
+ /**
110
+ * Get workflow by ID
111
+ */
112
+ async getWorkflow(id) {
113
+ return this.request("GET", `/workflows/${id}`);
114
+ }
115
+ /**
116
+ * Create a new workflow
117
+ */
118
+ async createWorkflow(data) {
119
+ return this.request("POST", "/workflows", data);
120
+ }
121
+ /**
122
+ * Update workflow
123
+ */
124
+ async updateWorkflow(id, data) {
125
+ return this.request("PUT", `/workflows/${id}`, data);
126
+ }
127
+ /**
128
+ * Delete workflow
129
+ */
130
+ async deleteWorkflow(id) {
131
+ return this.request("DELETE", `/workflows/${id}`);
132
+ }
133
+ // ===== WORKFLOW EXECUTION =====
134
+ /**
135
+ * Execute workflow
136
+ */
137
+ async executeWorkflow(id, data) {
138
+ return this.request("POST", `/workflows/${id}/execute`, data);
139
+ }
140
+ /**
141
+ * Get execution status
142
+ */
143
+ async getExecutionStatus(id) {
144
+ return this.request("GET", `/executions/${id}`);
145
+ }
146
+ /**
147
+ * Cancel execution
148
+ */
149
+ async cancelExecution(id) {
150
+ return this.request("POST", `/executions/${id}/cancel`);
151
+ }
152
+ // ===== IMPORT/EXPORT =====
153
+ /**
154
+ * Export workflow
155
+ */
156
+ async exportWorkflow(id, format = "json") {
157
+ return this.request("GET", `/workflows/${id}/export?format=${format}`);
158
+ }
159
+ /**
160
+ * Import workflow
161
+ */
162
+ async importWorkflow(workflow) {
163
+ return this.request("POST", "/workflows/import", workflow);
164
+ }
165
+ // ===== VALIDATION =====
166
+ /**
167
+ * Validate workflow
168
+ */
169
+ async validateWorkflow(workflow) {
170
+ return this.request("POST", "/workflows/validate", workflow);
171
+ }
172
+ // ===== UTILITY METHODS =====
173
+ /**
174
+ * Wait for execution completion
175
+ */
176
+ async waitForExecution(id, pollInterval = 1000) {
177
+ while (true) {
178
+ const status = await this.getExecutionStatus(id);
179
+ if (status.data?.status === "completed" || status.data?.status === "failed" || status.data?.status === "cancelled") {
180
+ return status;
181
+ }
182
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
183
+ }
184
+ }
185
+ /**
186
+ * Get workflows by tag
187
+ */
188
+ async getWorkflowsByTag(tag, query) {
189
+ return this.getWorkflows({ ...query, tags: tag });
190
+ }
191
+ /**
192
+ * Get node types by category
193
+ */
194
+ async getNodeTypesByCategory(category, query) {
195
+ return this.getNodeTypes({ ...query, category });
196
+ }
197
+ }
198
+ /**
199
+ * API Error Class
200
+ */
201
+ export class ApiError extends Error {
202
+ status;
203
+ code;
204
+ details;
205
+ constructor(status, message, code, details) {
206
+ super(message);
207
+ this.status = status;
208
+ this.code = code;
209
+ this.details = details;
210
+ this.name = "ApiError";
211
+ }
212
+ }
@@ -0,0 +1,237 @@
1
+ <!--
2
+ FlowDrop Demo Page
3
+ Demonstrates the FlowDrop library with sample data
4
+ Styled with BEM syntax
5
+ -->
6
+
7
+ <script lang="ts">
8
+ import { onMount } from "svelte";
9
+ import WorkflowEditor from "./WorkflowEditor.svelte";
10
+ import { api } from "../services/api.js";
11
+ import type { NodeMetadata, Workflow } from "../types/index.js";
12
+ import { getDefaultIcon } from '../utils/icons.js';
13
+
14
+ let nodes = $state<NodeMetadata[]>([]);
15
+ let workflow = $state<Workflow | undefined>(undefined);
16
+ let error = $state<string | null>(null);
17
+ let loading = $state(true);
18
+
19
+ /**
20
+ * Fetch node types from the server
21
+ */
22
+ async function fetchNodeTypes(): Promise<void> {
23
+ try {
24
+ loading = true;
25
+ error = null;
26
+
27
+ console.log("Fetching node types from server...");
28
+ const fetchedNodes = await api.nodes.getNodes();
29
+
30
+ console.log("✅ Fetched", fetchedNodes.length, "node types from server");
31
+ nodes = fetchedNodes;
32
+
33
+ } catch (err) {
34
+ console.error("❌ Failed to fetch node types:", err);
35
+ error = err instanceof Error ? err.message : "Failed to load node types";
36
+ } finally {
37
+ loading = false;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Retry loading node types
43
+ */
44
+ function retryLoad(): void {
45
+ fetchNodeTypes();
46
+ }
47
+
48
+ // Load node types on mount
49
+ onMount(() => {
50
+ fetchNodeTypes();
51
+ });
52
+
53
+ /**
54
+ * Handle workflow export
55
+ */
56
+ function handleWorkflowExport(): void {
57
+ if (!workflow) return;
58
+
59
+ const dataStr = JSON.stringify(workflow, null, 2);
60
+ const dataBlob = new Blob([dataStr], { type: "application/json" });
61
+ const url = URL.createObjectURL(dataBlob);
62
+ const link = document.createElement("a");
63
+ link.href = url;
64
+ link.download = `${workflow.name}.json`;
65
+ link.click();
66
+ URL.revokeObjectURL(url);
67
+ }
68
+ </script>
69
+
70
+ <svelte:head>
71
+ <title>FlowDrop - LLM Workflow Editor</title>
72
+ <meta name="description" content="A modern drag-and-drop workflow editor for LLM applications" />
73
+ </svelte:head>
74
+
75
+ <div class="flowdrop-app" style="min-height: 1200px;">
76
+ <!-- Header -->
77
+ <div class="flowdrop-navbar">
78
+ <div class="flowdrop-navbar__start">
79
+ <!-- Logo and Title -->
80
+ <div class="flowdrop-flex flowdrop-gap--3">
81
+ <div class="flowdrop-logo flowdrop-logo--header">
82
+ FD
83
+ </div>
84
+ <div>
85
+ <h1 class="flowdrop-text--lg flowdrop-font--bold">FlowDrop</h1>
86
+ <p class="flowdrop-text--xs flowdrop-text--gray">LLM Workflow Editor</p>
87
+ </div>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="flowdrop-navbar__center">
92
+ <!-- TODO: Add navigation -->
93
+ </div>
94
+
95
+ <div class="flowdrop-navbar__end">
96
+ <!-- TODO: Add user menu -->
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Main Content -->
101
+ <main class="flowdrop-main" style="height: calc(100vh - 60px);">
102
+ <!-- Status Display -->
103
+ {#if loading}
104
+ <div class="flowdrop-status flowdrop-status--loading">
105
+ <div class="flowdrop-status__content">
106
+ <div class="flowdrop-flex flowdrop-gap--3">
107
+ <div class="flowdrop-spinner"></div>
108
+ <span class="flowdrop-text--sm flowdrop-font--medium">Loading node types...</span>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ {:else if error}
113
+ <div class="flowdrop-status flowdrop-status--error">
114
+ <div class="flowdrop-status__content">
115
+ <div class="flowdrop-flex flowdrop-gap--3">
116
+ <div class="flowdrop-status__indicator flowdrop-status__indicator--error"></div>
117
+ <span class="flowdrop-text--sm flowdrop-font--medium">Error: {error}</span>
118
+ </div>
119
+ <div class="flowdrop-flex flowdrop-gap--2">
120
+ <button
121
+ class="flowdrop-btn flowdrop-btn--sm flowdrop-btn--outline"
122
+ onclick={retryLoad}
123
+ type="button"
124
+ >
125
+ Retry
126
+ </button>
127
+ <button
128
+ class="flowdrop-btn flowdrop-btn--ghost flowdrop-btn--sm"
129
+ onclick={() => error = null}
130
+ type="button"
131
+ >
132
+
133
+ </button>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ {/if}
138
+
139
+ <!-- Workflow Editor -->
140
+ <div class="flowdrop-editor-container" style="height: {(loading || error) ? 'calc(100% - 60px)' : '100%'};">
141
+ <WorkflowEditor
142
+ nodes={nodes}
143
+ workflow={workflow}
144
+ />
145
+ </div>
146
+ </main>
147
+ </div>
148
+
149
+ <style>
150
+ .flowdrop-app {
151
+ background: linear-gradient(135deg, #f9fafb 0%, #e0e7ff 50%, #c7d2fe 100%);
152
+ }
153
+
154
+ .flowdrop-navbar {
155
+ height: 60px;
156
+ display: flex;
157
+ align-items: center;
158
+ justify-content: space-between;
159
+ padding: 0 1rem;
160
+ background-color: #ffffff;
161
+ border-bottom: 1px solid #e5e7eb;
162
+ }
163
+
164
+ .flowdrop-navbar__start {
165
+ display: flex;
166
+ align-items: center;
167
+ }
168
+
169
+ .flowdrop-logo {
170
+ width: 2rem;
171
+ height: 2rem;
172
+ background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
173
+ border-radius: 0.5rem;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ color: #ffffff;
178
+ font-weight: 700;
179
+ font-size: 0.875rem;
180
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
181
+ }
182
+
183
+ .flowdrop-logo--header {
184
+ width: 40px;
185
+ height: 40px;
186
+ font-size: 1.25rem;
187
+ margin-top: 15px;
188
+ }
189
+
190
+ .flowdrop-main {
191
+ position: relative;
192
+ }
193
+
194
+ .flowdrop-status {
195
+ background-color: #eff6ff;
196
+ border-bottom: 1px solid #bfdbfe;
197
+ padding: 1rem;
198
+ }
199
+
200
+ .flowdrop-status--error {
201
+ background-color: #fef2f2;
202
+ border-bottom: 1px solid #fecaca;
203
+ }
204
+
205
+ .flowdrop-status__content {
206
+ max-width: 80rem;
207
+ margin: 0 auto;
208
+ display: flex;
209
+ align-items: center;
210
+ justify-content: space-between;
211
+ }
212
+
213
+ .flowdrop-status__indicator {
214
+ width: 0.5rem;
215
+ height: 0.5rem;
216
+ border-radius: 50%;
217
+ }
218
+
219
+ .flowdrop-status__indicator--error {
220
+ background-color: #ef4444;
221
+ }
222
+
223
+ .flowdrop-editor-container {
224
+ position: relative;
225
+ }
226
+
227
+ /* Ensure full height for screens larger than 1200px */
228
+ @media (min-height: 1200px) {
229
+ :global(html), :global(body) {
230
+ height: 100%;
231
+ }
232
+
233
+ :global(#svelte) {
234
+ height: 100%;
235
+ }
236
+ }
237
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const App: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type App = ReturnType<typeof App>;
3
+ export default App;
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import Icon from '@iconify/svelte';
3
+ export let title: string;
4
+ export let description: string;
5
+ export let iconName: string | undefined;
6
+ </script>
7
+
8
+ <div class="flowdrop-canvas-banner">
9
+ <div class="flowdrop-card">
10
+ <div class="flowdrop-card__body flowdrop-text--center">
11
+ <div class="flowdrop-canvas-banner__icon">
12
+ {#if iconName}
13
+ <Icon icon={iconName} class="flowdrop-canvas-banner__icon-svg" />
14
+ {/if}
15
+ </div>
16
+ <h3 class="flowdrop-canvas-banner__title">{title}</h3>
17
+ <p class="flowdrop-canvas-banner__description">{description}</p>
18
+ </div>
19
+ </div>
20
+ </div>
21
+
22
+ <style>
23
+ .flowdrop-canvas-banner {
24
+ position: absolute;
25
+ inset: 0;
26
+ display: flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ pointer-events: none;
30
+ }
31
+
32
+ .flowdrop-canvas-banner__icon {
33
+ font-size: 3.75rem;
34
+ margin-bottom: 1rem;
35
+ display: flex;
36
+ justify-content: center;
37
+ align-items: center;
38
+ }
39
+
40
+ .flowdrop-canvas-banner__title {
41
+ font-size: 1.125rem;
42
+ font-weight: 700;
43
+ margin-bottom: 0.5rem;
44
+ color: #111827;
45
+ }
46
+
47
+ .flowdrop-canvas-banner__description {
48
+ font-size: 0.875rem;
49
+ color: #6b7280;
50
+ }
51
+ </style>