@aspect-ops/exon-ui 0.2.0 → 0.2.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.
- package/README.md +200 -0
- package/dist/components/Accordion/Accordion.svelte +2 -2
- package/dist/components/Accordion/AccordionItem.svelte +2 -2
- package/dist/components/Chatbot/ChatMessage.svelte +143 -0
- package/dist/components/Chatbot/ChatMessage.svelte.d.ts +8 -0
- package/dist/components/Chatbot/Chatbot.svelte +640 -0
- package/dist/components/Chatbot/Chatbot.svelte.d.ts +22 -0
- package/dist/components/Chatbot/index.d.ts +3 -0
- package/dist/components/Chatbot/index.js +2 -0
- package/dist/components/Chatbot/types.d.ts +48 -0
- package/dist/components/Chatbot/types.js +2 -0
- package/dist/components/ContactForm/ContactForm.svelte +564 -0
- package/dist/components/ContactForm/ContactForm.svelte.d.ts +44 -0
- package/dist/components/ContactForm/index.d.ts +1 -0
- package/dist/components/ContactForm/index.js +1 -0
- package/dist/components/DoughnutChart/DoughnutChart.svelte +372 -0
- package/dist/components/DoughnutChart/DoughnutChart.svelte.d.ts +25 -0
- package/dist/components/DoughnutChart/index.d.ts +1 -0
- package/dist/components/DoughnutChart/index.js +1 -0
- package/dist/components/FAB/FAB.svelte +5 -1
- package/dist/components/FAB/FABGroup.svelte +10 -2
- package/dist/components/FileUpload/FileUpload.svelte +12 -12
- package/dist/components/Mermaid/Mermaid.svelte +206 -0
- package/dist/components/Mermaid/Mermaid.svelte.d.ts +28 -0
- package/dist/components/Mermaid/index.d.ts +1 -0
- package/dist/components/Mermaid/index.js +1 -0
- package/dist/components/Mermaid/mermaid.d.ts +21 -0
- package/dist/components/Popover/PopoverTrigger.svelte +1 -3
- package/dist/components/ViewCounter/ViewCounter.svelte +157 -0
- package/dist/components/ViewCounter/ViewCounter.svelte.d.ts +17 -0
- package/dist/components/ViewCounter/index.d.ts +1 -0
- package/dist/components/ViewCounter/index.js +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +9 -0
- package/dist/styles/tokens.css +2 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/input.d.ts +20 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1047,6 +1047,206 @@ Components optimized for Capacitor mobile apps with gesture support, haptic feed
|
|
|
1047
1047
|
|
|
1048
1048
|
- `edges` - Array of edges: `'top'` | `'right'` | `'bottom'` | `'left'` (default: all)
|
|
1049
1049
|
|
|
1050
|
+
## Website Components
|
|
1051
|
+
|
|
1052
|
+
### Chatbot
|
|
1053
|
+
|
|
1054
|
+
AI-ready chat widget with session management, typing indicators, and lead capture.
|
|
1055
|
+
|
|
1056
|
+
```svelte
|
|
1057
|
+
<script>
|
|
1058
|
+
import { Chatbot } from '@aspect-ops/exon-ui';
|
|
1059
|
+
|
|
1060
|
+
async function handleMessage(message, sessionId) {
|
|
1061
|
+
// Call your AI backend (OpenAI, Claude, etc.)
|
|
1062
|
+
const response = await fetch('/api/chat', {
|
|
1063
|
+
method: 'POST',
|
|
1064
|
+
body: JSON.stringify({ message, sessionId })
|
|
1065
|
+
});
|
|
1066
|
+
return (await response.json()).reply;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
async function handleLeadCapture(data, sessionId) {
|
|
1070
|
+
// Send to your CRM
|
|
1071
|
+
await fetch('/api/leads', {
|
|
1072
|
+
method: 'POST',
|
|
1073
|
+
body: JSON.stringify({ ...data, sessionId })
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
</script>
|
|
1077
|
+
|
|
1078
|
+
<Chatbot
|
|
1079
|
+
title="Support Chat"
|
|
1080
|
+
subtitle="We typically reply within minutes"
|
|
1081
|
+
welcomeMessage="Hello! How can I help you today?"
|
|
1082
|
+
onSendMessage={handleMessage}
|
|
1083
|
+
onLeadCapture={handleLeadCapture}
|
|
1084
|
+
position="bottom-right"
|
|
1085
|
+
/>
|
|
1086
|
+
```
|
|
1087
|
+
|
|
1088
|
+
**Props:**
|
|
1089
|
+
|
|
1090
|
+
| Prop | Type | Default | Description |
|
|
1091
|
+
| ------------------- | ---------- | ---------------- | ----------------------------------------- |
|
|
1092
|
+
| `title` | `string` | `'Chat Support'` | Chat header title |
|
|
1093
|
+
| `subtitle` | `string` | - | Chat header subtitle |
|
|
1094
|
+
| `welcomeMessage` | `string` | - | Initial welcome message |
|
|
1095
|
+
| `placeholder` | `string` | `'Type...'` | Input placeholder text |
|
|
1096
|
+
| `defaultOpen` | `boolean` | `false` | Open chat on mount |
|
|
1097
|
+
| `position` | `string` | `'bottom-right'` | Position: bottom-right, bottom-left |
|
|
1098
|
+
| `inactivityTimeout` | `number` | `40000` | Auto-open after inactivity (ms), 0=off |
|
|
1099
|
+
| `onSendMessage` | `function` | - | Callback: `(msg, sessionId) => reply` |
|
|
1100
|
+
| `onLeadCapture` | `function` | - | Callback: `(data, sessionId) => void` |
|
|
1101
|
+
| `onEscalate` | `function` | - | Callback: `(sessionId, messages) => void` |
|
|
1102
|
+
|
|
1103
|
+
### ContactForm
|
|
1104
|
+
|
|
1105
|
+
Customizable contact form with real-time validation and UTM tracking.
|
|
1106
|
+
|
|
1107
|
+
```svelte
|
|
1108
|
+
<script>
|
|
1109
|
+
import { ContactForm } from '@aspect-ops/exon-ui';
|
|
1110
|
+
|
|
1111
|
+
async function handleSubmit(data) {
|
|
1112
|
+
const response = await fetch('/api/contact', {
|
|
1113
|
+
method: 'POST',
|
|
1114
|
+
body: JSON.stringify(data)
|
|
1115
|
+
});
|
|
1116
|
+
return { success: response.ok };
|
|
1117
|
+
}
|
|
1118
|
+
</script>
|
|
1119
|
+
|
|
1120
|
+
<ContactForm
|
|
1121
|
+
submitText="Send Message"
|
|
1122
|
+
successMessage="Thank you! We'll be in touch soon."
|
|
1123
|
+
onSubmit={handleSubmit}
|
|
1124
|
+
/>
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
**Props:**
|
|
1128
|
+
|
|
1129
|
+
| Prop | Type | Default | Description |
|
|
1130
|
+
| ------------------ | --------------- | -------------- | ------------------------------------- |
|
|
1131
|
+
| `fields` | `FieldConfig[]` | Default fields | Array of field configurations |
|
|
1132
|
+
| `initialValues` | `object` | `{}` | Initial form values |
|
|
1133
|
+
| `submitText` | `string` | `'Submit'` | Submit button text |
|
|
1134
|
+
| `successMessage` | `string` | `'Thank you!'` | Success message after submit |
|
|
1135
|
+
| `privacyNotice` | `string` | - | Privacy policy text |
|
|
1136
|
+
| `extractUtmParams` | `boolean` | `true` | Extract UTM params from URL |
|
|
1137
|
+
| `onSubmit` | `function` | - | Callback: `(data) => { success }` |
|
|
1138
|
+
| `onValidate` | `function` | - | Callback: `(isValid, errors) => void` |
|
|
1139
|
+
|
|
1140
|
+
**Default fields:** firstName, lastName, email, phone, company, message
|
|
1141
|
+
|
|
1142
|
+
### ViewCounter
|
|
1143
|
+
|
|
1144
|
+
Track and display page views with session-based deduplication.
|
|
1145
|
+
|
|
1146
|
+
```svelte
|
|
1147
|
+
<script>
|
|
1148
|
+
import { ViewCounter } from '@aspect-ops/exon-ui';
|
|
1149
|
+
|
|
1150
|
+
async function getCount(slug) {
|
|
1151
|
+
const res = await fetch(`/api/views/${slug}`);
|
|
1152
|
+
return (await res.json()).count;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async function trackView(slug) {
|
|
1156
|
+
const res = await fetch(`/api/views/${slug}`, { method: 'POST' });
|
|
1157
|
+
return (await res.json()).count;
|
|
1158
|
+
}
|
|
1159
|
+
</script>
|
|
1160
|
+
|
|
1161
|
+
<ViewCounter slug="blog-post-123" onGetCount={getCount} onTrackView={trackView} />
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
**Props:**
|
|
1165
|
+
|
|
1166
|
+
| Prop | Type | Default | Description |
|
|
1167
|
+
| ------------- | ---------- | ------- | ---------------------------- |
|
|
1168
|
+
| `slug` | `string` | - | Unique content identifier |
|
|
1169
|
+
| `showLabel` | `boolean` | `true` | Show "views" label |
|
|
1170
|
+
| `trackView` | `boolean` | `true` | Track view (or just display) |
|
|
1171
|
+
| `onGetCount` | `function` | - | Callback: `(slug) => count` |
|
|
1172
|
+
| `onTrackView` | `function` | - | Callback: `(slug) => count` |
|
|
1173
|
+
|
|
1174
|
+
### DoughnutChart
|
|
1175
|
+
|
|
1176
|
+
Pure SVG doughnut chart with interactive legend (no external dependencies).
|
|
1177
|
+
|
|
1178
|
+
```svelte
|
|
1179
|
+
<script>
|
|
1180
|
+
import { DoughnutChart } from '@aspect-ops/exon-ui';
|
|
1181
|
+
|
|
1182
|
+
const data = [
|
|
1183
|
+
{ label: 'Direct', value: 45000 },
|
|
1184
|
+
{ label: 'Referral', value: 28000 },
|
|
1185
|
+
{ label: 'Organic', value: 32000 },
|
|
1186
|
+
{ label: 'Social', value: 15000 }
|
|
1187
|
+
];
|
|
1188
|
+
</script>
|
|
1189
|
+
|
|
1190
|
+
<DoughnutChart title="Traffic Sources" {data} size={200} showLegend showTotal />
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
**Props:**
|
|
1194
|
+
|
|
1195
|
+
| Prop | Type | Default | Description |
|
|
1196
|
+
| ------------ | ---------- | --------------- | ------------------------- |
|
|
1197
|
+
| `title` | `string` | - | Chart title |
|
|
1198
|
+
| `data` | `array` | `[]` | `[{ label, value }, ...]` |
|
|
1199
|
+
| `size` | `number` | `200` | Chart size in pixels |
|
|
1200
|
+
| `thickness` | `number` | `0.6` | Doughnut thickness (0-1) |
|
|
1201
|
+
| `colors` | `string[]` | Default palette | Custom color array |
|
|
1202
|
+
| `showLegend` | `boolean` | `true` | Show legend |
|
|
1203
|
+
| `showTotal` | `boolean` | `true` | Show center total |
|
|
1204
|
+
|
|
1205
|
+
### Mermaid
|
|
1206
|
+
|
|
1207
|
+
Render Mermaid diagrams with customizable theming.
|
|
1208
|
+
|
|
1209
|
+
**Note:** Requires `mermaid` package: `npm install mermaid`
|
|
1210
|
+
|
|
1211
|
+
```svelte
|
|
1212
|
+
<script>
|
|
1213
|
+
import { Mermaid } from '@aspect-ops/exon-ui';
|
|
1214
|
+
|
|
1215
|
+
const diagram = `flowchart TD
|
|
1216
|
+
A[Start] --> B{Decision}
|
|
1217
|
+
B -->|Yes| C[Action 1]
|
|
1218
|
+
B -->|No| D[Action 2]
|
|
1219
|
+
C --> E[End]
|
|
1220
|
+
D --> E`;
|
|
1221
|
+
</script>
|
|
1222
|
+
|
|
1223
|
+
<!-- Using code prop -->
|
|
1224
|
+
<Mermaid code={diagram} />
|
|
1225
|
+
|
|
1226
|
+
<!-- Using slot -->
|
|
1227
|
+
<Mermaid>flowchart LR A --> B --> C</Mermaid>
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
**Props:**
|
|
1231
|
+
|
|
1232
|
+
| Prop | Type | Default | Description |
|
|
1233
|
+
| ------- | -------- | ------------- | ---------------------- |
|
|
1234
|
+
| `code` | `string` | - | Mermaid diagram code |
|
|
1235
|
+
| `theme` | `object` | ExonPro theme | Custom theme variables |
|
|
1236
|
+
|
|
1237
|
+
**Theme variables:**
|
|
1238
|
+
|
|
1239
|
+
```javascript
|
|
1240
|
+
const theme = {
|
|
1241
|
+
primaryColor: '#4654A3',
|
|
1242
|
+
primaryTextColor: '#270949',
|
|
1243
|
+
primaryBorderColor: '#4654A3',
|
|
1244
|
+
lineColor: '#270949',
|
|
1245
|
+
secondaryColor: '#FF3131',
|
|
1246
|
+
tertiaryColor: '#f3f4f6'
|
|
1247
|
+
};
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1050
1250
|
## Theming & Customization
|
|
1051
1251
|
|
|
1052
1252
|
### Quick Setup
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
let { value, disabled = false, class: className = '', header, children }: Props = $props();
|
|
13
13
|
|
|
14
|
-
// Context key (must match parent)
|
|
15
|
-
const ACCORDION_KEY =
|
|
14
|
+
// Context key (must match parent Accordion)
|
|
15
|
+
const ACCORDION_KEY = 'exon-accordion-context';
|
|
16
16
|
|
|
17
17
|
// Get context from parent Accordion
|
|
18
18
|
const ctx = getContext<{
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ChatMessage } from './types.js';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
message: ChatMessage;
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { message, class: className = '' }: Props = $props();
|
|
10
|
+
|
|
11
|
+
// Format message content (bold, links)
|
|
12
|
+
function formatContent(content: string): string {
|
|
13
|
+
// Convert **text** to bold
|
|
14
|
+
let formatted = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
15
|
+
// Convert [text](url) to links
|
|
16
|
+
formatted = formatted.replace(
|
|
17
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
18
|
+
'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
|
|
19
|
+
);
|
|
20
|
+
return formatted;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const formattedContent = $derived(formatContent(message.content));
|
|
24
|
+
const isUser = $derived(message.role === 'user');
|
|
25
|
+
const isSystem = $derived(message.role === 'system');
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<div
|
|
29
|
+
class="chat-message chat-message--{message.role} {className}"
|
|
30
|
+
class:chat-message--user={isUser}
|
|
31
|
+
class:chat-message--assistant={!isUser && !isSystem}
|
|
32
|
+
class:chat-message--system={isSystem}
|
|
33
|
+
>
|
|
34
|
+
{#if !isUser && !isSystem}
|
|
35
|
+
<div class="chat-message__avatar">
|
|
36
|
+
<svg
|
|
37
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
38
|
+
width="20"
|
|
39
|
+
height="20"
|
|
40
|
+
viewBox="0 0 24 24"
|
|
41
|
+
fill="none"
|
|
42
|
+
stroke="currentColor"
|
|
43
|
+
stroke-width="2"
|
|
44
|
+
stroke-linecap="round"
|
|
45
|
+
stroke-linejoin="round"
|
|
46
|
+
>
|
|
47
|
+
<path d="M12 8V4H8" />
|
|
48
|
+
<rect width="16" height="12" x="4" y="8" rx="2" />
|
|
49
|
+
<path d="M2 14h2" />
|
|
50
|
+
<path d="M20 14h2" />
|
|
51
|
+
<path d="M15 13v2" />
|
|
52
|
+
<path d="M9 13v2" />
|
|
53
|
+
</svg>
|
|
54
|
+
</div>
|
|
55
|
+
{/if}
|
|
56
|
+
<div class="chat-message__bubble">
|
|
57
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
58
|
+
{@html formattedContent}
|
|
59
|
+
</div>
|
|
60
|
+
{#if message.timestamp}
|
|
61
|
+
<div class="chat-message__time">
|
|
62
|
+
{new Date(message.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
|
63
|
+
</div>
|
|
64
|
+
{/if}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<style>
|
|
68
|
+
.chat-message {
|
|
69
|
+
display: flex;
|
|
70
|
+
gap: var(--space-sm, 0.5rem);
|
|
71
|
+
max-width: 85%;
|
|
72
|
+
margin-bottom: var(--space-sm, 0.5rem);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.chat-message--user {
|
|
76
|
+
margin-left: auto;
|
|
77
|
+
flex-direction: row-reverse;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.chat-message--assistant {
|
|
81
|
+
margin-right: auto;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.chat-message--system {
|
|
85
|
+
margin: var(--space-sm, 0.5rem) auto;
|
|
86
|
+
max-width: 90%;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.chat-message__avatar {
|
|
90
|
+
flex-shrink: 0;
|
|
91
|
+
width: 2rem;
|
|
92
|
+
height: 2rem;
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
border-radius: var(--radius-full, 9999px);
|
|
97
|
+
background: var(--color-primary, #3b82f6);
|
|
98
|
+
color: var(--color-text-inverse, #ffffff);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.chat-message__bubble {
|
|
102
|
+
padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
|
|
103
|
+
border-radius: var(--radius-lg, 0.75rem);
|
|
104
|
+
font-size: var(--text-sm, 0.875rem);
|
|
105
|
+
line-height: var(--leading-relaxed, 1.625);
|
|
106
|
+
word-wrap: break-word;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.chat-message--user .chat-message__bubble {
|
|
110
|
+
background: var(--color-primary, #3b82f6);
|
|
111
|
+
color: var(--color-text-inverse, #ffffff);
|
|
112
|
+
border-bottom-right-radius: var(--radius-sm, 0.25rem);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.chat-message--assistant .chat-message__bubble {
|
|
116
|
+
background: var(--color-bg-muted, #f3f4f6);
|
|
117
|
+
color: var(--color-text, #1f2937);
|
|
118
|
+
border-bottom-left-radius: var(--radius-sm, 0.25rem);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.chat-message--system .chat-message__bubble {
|
|
122
|
+
background: var(--color-bg-elevated, #ffffff);
|
|
123
|
+
border: 1px solid var(--color-border, #e5e7eb);
|
|
124
|
+
color: var(--color-text-muted, #6b7280);
|
|
125
|
+
font-style: italic;
|
|
126
|
+
text-align: center;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.chat-message__bubble :global(a) {
|
|
130
|
+
color: inherit;
|
|
131
|
+
text-decoration: underline;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.chat-message__bubble :global(strong) {
|
|
135
|
+
font-weight: var(--font-semibold, 600);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.chat-message__time {
|
|
139
|
+
align-self: flex-end;
|
|
140
|
+
font-size: var(--text-xs, 0.75rem);
|
|
141
|
+
color: var(--color-text-subtle, #9ca3af);
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ChatMessage } from './types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
message: ChatMessage;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const ChatMessage: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ChatMessage = ReturnType<typeof ChatMessage>;
|
|
8
|
+
export default ChatMessage;
|