freesail 0.0.1 → 0.1.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 +190 -5
- package/docs/A2UX_Protocol.md +183 -0
- package/docs/Agents.md +218 -0
- package/docs/Architecture.md +285 -0
- package/docs/CatalogReference.md +377 -0
- package/docs/GettingStarted.md +230 -0
- package/examples/demo/package.json +21 -0
- package/examples/demo/public/index.html +381 -0
- package/examples/demo/server.js +253 -0
- package/package.json +38 -5
- package/packages/core/package.json +48 -0
- package/packages/core/src/functions.ts +403 -0
- package/packages/core/src/index.ts +214 -0
- package/packages/core/src/parser.ts +270 -0
- package/packages/core/src/protocol.ts +254 -0
- package/packages/core/src/store.ts +452 -0
- package/packages/core/src/transport.ts +439 -0
- package/packages/core/src/types.ts +209 -0
- package/packages/core/tsconfig.json +10 -0
- package/packages/lit-ui/package.json +44 -0
- package/packages/lit-ui/src/catalogs/standard/catalog.json +405 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Badge.ts +96 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Button.ts +147 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Card.ts +78 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Checkbox.ts +94 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Column.ts +66 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Divider.ts +59 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Image.ts +54 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Input.ts +125 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Progress.ts +79 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Row.ts +68 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Select.ts +110 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Spacer.ts +37 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Spinner.ts +76 -0
- package/packages/lit-ui/src/catalogs/standard/elements/Text.ts +86 -0
- package/packages/lit-ui/src/catalogs/standard/elements/index.ts +18 -0
- package/packages/lit-ui/src/catalogs/standard/index.ts +17 -0
- package/packages/lit-ui/src/index.ts +84 -0
- package/packages/lit-ui/src/renderer.ts +211 -0
- package/packages/lit-ui/src/types.ts +49 -0
- package/packages/lit-ui/src/utils/define-props.ts +157 -0
- package/packages/lit-ui/src/utils/index.ts +2 -0
- package/packages/lit-ui/src/utils/registry.ts +139 -0
- package/packages/lit-ui/tsconfig.json +11 -0
- package/packages/server/package.json +61 -0
- package/packages/server/src/adapters/index.ts +5 -0
- package/packages/server/src/adapters/langchain.ts +175 -0
- package/packages/server/src/adapters/openai.ts +209 -0
- package/packages/server/src/catalog-loader.ts +311 -0
- package/packages/server/src/index.ts +142 -0
- package/packages/server/src/stream.ts +329 -0
- package/packages/server/tsconfig.json +11 -0
- package/tsconfig.base.json +23 -0
- package/index.js +0 -3
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "standard_catalog_v1",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"name": "Freesail Standard Catalog",
|
|
5
|
+
"description": "Standard A2UI components for cross-compatible generative UI",
|
|
6
|
+
"components": {
|
|
7
|
+
"Column": {
|
|
8
|
+
"description": "A vertical flex container that stacks children vertically",
|
|
9
|
+
"properties": {
|
|
10
|
+
"gap": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Gap between child elements (CSS value)",
|
|
13
|
+
"default": "8px"
|
|
14
|
+
},
|
|
15
|
+
"align": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Alignment of children (start, center, end, stretch)",
|
|
18
|
+
"default": "stretch",
|
|
19
|
+
"enum": ["start", "center", "end", "stretch"]
|
|
20
|
+
},
|
|
21
|
+
"justify": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Justification of children (start, center, end, between, around)",
|
|
24
|
+
"default": "start",
|
|
25
|
+
"enum": ["start", "center", "end", "between", "around"]
|
|
26
|
+
},
|
|
27
|
+
"padding": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Padding inside the column (CSS value)",
|
|
30
|
+
"default": "0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"Row": {
|
|
35
|
+
"description": "A horizontal flex container that arranges children horizontally",
|
|
36
|
+
"properties": {
|
|
37
|
+
"gap": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "Gap between child elements (CSS value)",
|
|
40
|
+
"default": "8px"
|
|
41
|
+
},
|
|
42
|
+
"align": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Alignment of children (start, center, end, stretch)",
|
|
45
|
+
"default": "center",
|
|
46
|
+
"enum": ["start", "center", "end", "stretch"]
|
|
47
|
+
},
|
|
48
|
+
"justify": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Justification of children (start, center, end, between, around)",
|
|
51
|
+
"default": "start",
|
|
52
|
+
"enum": ["start", "center", "end", "between", "around"]
|
|
53
|
+
},
|
|
54
|
+
"wrap": {
|
|
55
|
+
"type": "boolean",
|
|
56
|
+
"description": "Whether children should wrap to next line",
|
|
57
|
+
"default": false
|
|
58
|
+
},
|
|
59
|
+
"padding": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"description": "Padding inside the row (CSS value)",
|
|
62
|
+
"default": "0"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"Text": {
|
|
67
|
+
"description": "A text display component",
|
|
68
|
+
"properties": {
|
|
69
|
+
"text": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"description": "The text content to display",
|
|
72
|
+
"default": ""
|
|
73
|
+
},
|
|
74
|
+
"variant": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"description": "Text style variant",
|
|
77
|
+
"default": "body",
|
|
78
|
+
"enum": ["h1", "h2", "h3", "h4", "h5", "h6", "body", "caption", "overline"]
|
|
79
|
+
},
|
|
80
|
+
"color": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"description": "Text color (CSS value)",
|
|
83
|
+
"default": "inherit"
|
|
84
|
+
},
|
|
85
|
+
"weight": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"description": "Font weight",
|
|
88
|
+
"default": "normal",
|
|
89
|
+
"enum": ["light", "normal", "medium", "semibold", "bold"]
|
|
90
|
+
},
|
|
91
|
+
"align": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"description": "Text alignment",
|
|
94
|
+
"default": "left",
|
|
95
|
+
"enum": ["left", "center", "right", "justify"]
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"required": ["text"]
|
|
99
|
+
},
|
|
100
|
+
"Button": {
|
|
101
|
+
"description": "An interactive button component",
|
|
102
|
+
"properties": {
|
|
103
|
+
"label": {
|
|
104
|
+
"type": "string",
|
|
105
|
+
"description": "Button label text",
|
|
106
|
+
"default": "Button"
|
|
107
|
+
},
|
|
108
|
+
"variant": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"description": "Button style variant",
|
|
111
|
+
"default": "primary",
|
|
112
|
+
"enum": ["primary", "secondary", "outline", "ghost", "destructive"]
|
|
113
|
+
},
|
|
114
|
+
"size": {
|
|
115
|
+
"type": "string",
|
|
116
|
+
"description": "Button size",
|
|
117
|
+
"default": "medium",
|
|
118
|
+
"enum": ["small", "medium", "large"]
|
|
119
|
+
},
|
|
120
|
+
"disabled": {
|
|
121
|
+
"type": "boolean",
|
|
122
|
+
"description": "Whether the button is disabled",
|
|
123
|
+
"default": false
|
|
124
|
+
},
|
|
125
|
+
"action": {
|
|
126
|
+
"type": "string",
|
|
127
|
+
"description": "Action identifier sent on click"
|
|
128
|
+
},
|
|
129
|
+
"fullWidth": {
|
|
130
|
+
"type": "boolean",
|
|
131
|
+
"description": "Whether button takes full width",
|
|
132
|
+
"default": false
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"required": ["label"],
|
|
136
|
+
"events": {
|
|
137
|
+
"click": {
|
|
138
|
+
"description": "Fired when the button is clicked"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"Input": {
|
|
143
|
+
"description": "A text input field",
|
|
144
|
+
"properties": {
|
|
145
|
+
"value": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"description": "Current input value",
|
|
148
|
+
"default": ""
|
|
149
|
+
},
|
|
150
|
+
"placeholder": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"description": "Placeholder text",
|
|
153
|
+
"default": ""
|
|
154
|
+
},
|
|
155
|
+
"label": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"description": "Input label"
|
|
158
|
+
},
|
|
159
|
+
"type": {
|
|
160
|
+
"type": "string",
|
|
161
|
+
"description": "Input type",
|
|
162
|
+
"default": "text",
|
|
163
|
+
"enum": ["text", "email", "password", "number", "tel", "url"]
|
|
164
|
+
},
|
|
165
|
+
"disabled": {
|
|
166
|
+
"type": "boolean",
|
|
167
|
+
"description": "Whether the input is disabled",
|
|
168
|
+
"default": false
|
|
169
|
+
},
|
|
170
|
+
"required": {
|
|
171
|
+
"type": "boolean",
|
|
172
|
+
"description": "Whether the input is required",
|
|
173
|
+
"default": false
|
|
174
|
+
},
|
|
175
|
+
"error": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"description": "Error message to display"
|
|
178
|
+
},
|
|
179
|
+
"bindPath": {
|
|
180
|
+
"type": "string",
|
|
181
|
+
"description": "JSON Pointer path for data binding"
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
"events": {
|
|
185
|
+
"change": {
|
|
186
|
+
"description": "Fired when the input value changes"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"Card": {
|
|
191
|
+
"description": "A card container with optional header and footer",
|
|
192
|
+
"properties": {
|
|
193
|
+
"title": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"description": "Card title"
|
|
196
|
+
},
|
|
197
|
+
"subtitle": {
|
|
198
|
+
"type": "string",
|
|
199
|
+
"description": "Card subtitle"
|
|
200
|
+
},
|
|
201
|
+
"padding": {
|
|
202
|
+
"type": "string",
|
|
203
|
+
"description": "Card padding (CSS value)",
|
|
204
|
+
"default": "16px"
|
|
205
|
+
},
|
|
206
|
+
"elevated": {
|
|
207
|
+
"type": "boolean",
|
|
208
|
+
"description": "Whether to show elevation shadow",
|
|
209
|
+
"default": true
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"Image": {
|
|
214
|
+
"description": "An image display component",
|
|
215
|
+
"properties": {
|
|
216
|
+
"src": {
|
|
217
|
+
"type": "string",
|
|
218
|
+
"description": "Image source URL"
|
|
219
|
+
},
|
|
220
|
+
"alt": {
|
|
221
|
+
"type": "string",
|
|
222
|
+
"description": "Alternative text for accessibility",
|
|
223
|
+
"default": ""
|
|
224
|
+
},
|
|
225
|
+
"width": {
|
|
226
|
+
"type": "string",
|
|
227
|
+
"description": "Image width (CSS value)"
|
|
228
|
+
},
|
|
229
|
+
"height": {
|
|
230
|
+
"type": "string",
|
|
231
|
+
"description": "Image height (CSS value)"
|
|
232
|
+
},
|
|
233
|
+
"fit": {
|
|
234
|
+
"type": "string",
|
|
235
|
+
"description": "Object fit mode",
|
|
236
|
+
"default": "cover",
|
|
237
|
+
"enum": ["contain", "cover", "fill", "none", "scale-down"]
|
|
238
|
+
},
|
|
239
|
+
"borderRadius": {
|
|
240
|
+
"type": "string",
|
|
241
|
+
"description": "Border radius (CSS value)",
|
|
242
|
+
"default": "0"
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"required": ["src"]
|
|
246
|
+
},
|
|
247
|
+
"Divider": {
|
|
248
|
+
"description": "A horizontal or vertical divider line",
|
|
249
|
+
"properties": {
|
|
250
|
+
"orientation": {
|
|
251
|
+
"type": "string",
|
|
252
|
+
"description": "Divider orientation",
|
|
253
|
+
"default": "horizontal",
|
|
254
|
+
"enum": ["horizontal", "vertical"]
|
|
255
|
+
},
|
|
256
|
+
"thickness": {
|
|
257
|
+
"type": "string",
|
|
258
|
+
"description": "Divider thickness (CSS value)",
|
|
259
|
+
"default": "1px"
|
|
260
|
+
},
|
|
261
|
+
"color": {
|
|
262
|
+
"type": "string",
|
|
263
|
+
"description": "Divider color (CSS value)",
|
|
264
|
+
"default": "#e0e0e0"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"Spacer": {
|
|
269
|
+
"description": "A flexible space component for layout",
|
|
270
|
+
"properties": {
|
|
271
|
+
"size": {
|
|
272
|
+
"type": "string",
|
|
273
|
+
"description": "Fixed size (CSS value), or 'flex' for flexible space",
|
|
274
|
+
"default": "flex"
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
"Badge": {
|
|
279
|
+
"description": "A badge/chip component for status or labels",
|
|
280
|
+
"properties": {
|
|
281
|
+
"text": {
|
|
282
|
+
"type": "string",
|
|
283
|
+
"description": "Badge text"
|
|
284
|
+
},
|
|
285
|
+
"variant": {
|
|
286
|
+
"type": "string",
|
|
287
|
+
"description": "Badge style variant",
|
|
288
|
+
"default": "default",
|
|
289
|
+
"enum": ["default", "primary", "success", "warning", "error", "info"]
|
|
290
|
+
},
|
|
291
|
+
"size": {
|
|
292
|
+
"type": "string",
|
|
293
|
+
"description": "Badge size",
|
|
294
|
+
"default": "medium",
|
|
295
|
+
"enum": ["small", "medium", "large"]
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
"required": ["text"]
|
|
299
|
+
},
|
|
300
|
+
"Checkbox": {
|
|
301
|
+
"description": "A checkbox input component",
|
|
302
|
+
"properties": {
|
|
303
|
+
"checked": {
|
|
304
|
+
"type": "boolean",
|
|
305
|
+
"description": "Whether the checkbox is checked",
|
|
306
|
+
"default": false
|
|
307
|
+
},
|
|
308
|
+
"label": {
|
|
309
|
+
"type": "string",
|
|
310
|
+
"description": "Checkbox label"
|
|
311
|
+
},
|
|
312
|
+
"disabled": {
|
|
313
|
+
"type": "boolean",
|
|
314
|
+
"description": "Whether the checkbox is disabled",
|
|
315
|
+
"default": false
|
|
316
|
+
},
|
|
317
|
+
"bindPath": {
|
|
318
|
+
"type": "string",
|
|
319
|
+
"description": "JSON Pointer path for data binding"
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
"events": {
|
|
323
|
+
"change": {
|
|
324
|
+
"description": "Fired when the checkbox state changes"
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
"Select": {
|
|
329
|
+
"description": "A dropdown select component",
|
|
330
|
+
"properties": {
|
|
331
|
+
"value": {
|
|
332
|
+
"type": "string",
|
|
333
|
+
"description": "Currently selected value"
|
|
334
|
+
},
|
|
335
|
+
"options": {
|
|
336
|
+
"type": "array",
|
|
337
|
+
"description": "Array of options [{value, label}]"
|
|
338
|
+
},
|
|
339
|
+
"placeholder": {
|
|
340
|
+
"type": "string",
|
|
341
|
+
"description": "Placeholder text",
|
|
342
|
+
"default": "Select an option"
|
|
343
|
+
},
|
|
344
|
+
"label": {
|
|
345
|
+
"type": "string",
|
|
346
|
+
"description": "Select label"
|
|
347
|
+
},
|
|
348
|
+
"disabled": {
|
|
349
|
+
"type": "boolean",
|
|
350
|
+
"description": "Whether the select is disabled",
|
|
351
|
+
"default": false
|
|
352
|
+
},
|
|
353
|
+
"bindPath": {
|
|
354
|
+
"type": "string",
|
|
355
|
+
"description": "JSON Pointer path for data binding"
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
"events": {
|
|
359
|
+
"change": {
|
|
360
|
+
"description": "Fired when selection changes"
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
"Spinner": {
|
|
365
|
+
"description": "A loading spinner component",
|
|
366
|
+
"properties": {
|
|
367
|
+
"size": {
|
|
368
|
+
"type": "string",
|
|
369
|
+
"description": "Spinner size",
|
|
370
|
+
"default": "medium",
|
|
371
|
+
"enum": ["small", "medium", "large"]
|
|
372
|
+
},
|
|
373
|
+
"color": {
|
|
374
|
+
"type": "string",
|
|
375
|
+
"description": "Spinner color (CSS value)",
|
|
376
|
+
"default": "currentColor"
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
"Progress": {
|
|
381
|
+
"description": "A progress bar component",
|
|
382
|
+
"properties": {
|
|
383
|
+
"value": {
|
|
384
|
+
"type": "number",
|
|
385
|
+
"description": "Progress value (0-100)",
|
|
386
|
+
"default": 0
|
|
387
|
+
},
|
|
388
|
+
"max": {
|
|
389
|
+
"type": "number",
|
|
390
|
+
"description": "Maximum value",
|
|
391
|
+
"default": 100
|
|
392
|
+
},
|
|
393
|
+
"showLabel": {
|
|
394
|
+
"type": "boolean",
|
|
395
|
+
"description": "Whether to show percentage label",
|
|
396
|
+
"default": false
|
|
397
|
+
},
|
|
398
|
+
"color": {
|
|
399
|
+
"type": "string",
|
|
400
|
+
"description": "Progress bar color (CSS value)"
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Badge Component
|
|
3
|
+
*
|
|
4
|
+
* A badge/chip component for status or labels.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { LitElement, html, css } from 'lit';
|
|
8
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
9
|
+
|
|
10
|
+
type BadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info';
|
|
11
|
+
type BadgeSize = 'small' | 'medium' | 'large';
|
|
12
|
+
|
|
13
|
+
@customElement('fs-badge')
|
|
14
|
+
export class FreesailBadge extends LitElement {
|
|
15
|
+
static override styles = css`
|
|
16
|
+
:host {
|
|
17
|
+
display: inline-block;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.badge {
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
border-radius: 9999px;
|
|
25
|
+
font-weight: 500;
|
|
26
|
+
white-space: nowrap;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Sizes */
|
|
30
|
+
.size-small {
|
|
31
|
+
padding: 2px 8px;
|
|
32
|
+
font-size: 0.75rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.size-medium {
|
|
36
|
+
padding: 4px 12px;
|
|
37
|
+
font-size: 0.875rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.size-large {
|
|
41
|
+
padding: 6px 16px;
|
|
42
|
+
font-size: 1rem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Variants */
|
|
46
|
+
.variant-default {
|
|
47
|
+
background: #e5e7eb;
|
|
48
|
+
color: #374151;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.variant-primary {
|
|
52
|
+
background: #dbeafe;
|
|
53
|
+
color: #1d4ed8;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.variant-success {
|
|
57
|
+
background: #dcfce7;
|
|
58
|
+
color: #15803d;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.variant-warning {
|
|
62
|
+
background: #fef3c7;
|
|
63
|
+
color: #b45309;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.variant-error {
|
|
67
|
+
background: #fee2e2;
|
|
68
|
+
color: #b91c1c;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.variant-info {
|
|
72
|
+
background: #e0f2fe;
|
|
73
|
+
color: #0369a1;
|
|
74
|
+
}
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
@property({ type: String }) text = '';
|
|
78
|
+
@property({ type: String }) variant: BadgeVariant = 'default';
|
|
79
|
+
@property({ type: String }) size: BadgeSize = 'medium';
|
|
80
|
+
|
|
81
|
+
override render() {
|
|
82
|
+
const classes = `badge variant-${this.variant} size-${this.size}`;
|
|
83
|
+
|
|
84
|
+
return html`
|
|
85
|
+
<span class=${classes}>
|
|
86
|
+
${this.text}
|
|
87
|
+
</span>
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
declare global {
|
|
93
|
+
interface HTMLElementTagNameMap {
|
|
94
|
+
'fs-badge': FreesailBadge;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Component
|
|
3
|
+
*
|
|
4
|
+
* An interactive button component with variants.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { LitElement, html, css } from 'lit';
|
|
8
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
9
|
+
|
|
10
|
+
type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive';
|
|
11
|
+
type ButtonSize = 'small' | 'medium' | 'large';
|
|
12
|
+
|
|
13
|
+
@customElement('fs-button')
|
|
14
|
+
export class FreesailButton extends LitElement {
|
|
15
|
+
static override styles = css`
|
|
16
|
+
:host {
|
|
17
|
+
display: inline-block;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
:host([fullWidth]) {
|
|
21
|
+
display: block;
|
|
22
|
+
width: 100%;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
button {
|
|
26
|
+
font-family: inherit;
|
|
27
|
+
font-weight: 500;
|
|
28
|
+
border: none;
|
|
29
|
+
border-radius: 6px;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
transition: all 0.2s ease;
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
gap: 8px;
|
|
36
|
+
width: 100%;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
button:disabled {
|
|
40
|
+
opacity: 0.5;
|
|
41
|
+
cursor: not-allowed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Sizes */
|
|
45
|
+
.size-small {
|
|
46
|
+
padding: 6px 12px;
|
|
47
|
+
font-size: 0.875rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.size-medium {
|
|
51
|
+
padding: 10px 20px;
|
|
52
|
+
font-size: 1rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.size-large {
|
|
56
|
+
padding: 14px 28px;
|
|
57
|
+
font-size: 1.125rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Variants */
|
|
61
|
+
.variant-primary {
|
|
62
|
+
background: #2563eb;
|
|
63
|
+
color: white;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.variant-primary:hover:not(:disabled) {
|
|
67
|
+
background: #1d4ed8;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.variant-secondary {
|
|
71
|
+
background: #6b7280;
|
|
72
|
+
color: white;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.variant-secondary:hover:not(:disabled) {
|
|
76
|
+
background: #4b5563;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.variant-outline {
|
|
80
|
+
background: transparent;
|
|
81
|
+
color: #2563eb;
|
|
82
|
+
border: 2px solid #2563eb;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.variant-outline:hover:not(:disabled) {
|
|
86
|
+
background: #eff6ff;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.variant-ghost {
|
|
90
|
+
background: transparent;
|
|
91
|
+
color: #374151;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.variant-ghost:hover:not(:disabled) {
|
|
95
|
+
background: #f3f4f6;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.variant-destructive {
|
|
99
|
+
background: #dc2626;
|
|
100
|
+
color: white;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.variant-destructive:hover:not(:disabled) {
|
|
104
|
+
background: #b91c1c;
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
@property({ type: String }) label = 'Button';
|
|
109
|
+
@property({ type: String }) variant: ButtonVariant = 'primary';
|
|
110
|
+
@property({ type: String }) size: ButtonSize = 'medium';
|
|
111
|
+
@property({ type: Boolean }) disabled = false;
|
|
112
|
+
@property({ type: String }) action = '';
|
|
113
|
+
@property({ type: Boolean, reflect: true }) fullWidth = false;
|
|
114
|
+
|
|
115
|
+
private handleClick() {
|
|
116
|
+
if (this.disabled) return;
|
|
117
|
+
|
|
118
|
+
this.dispatchEvent(new CustomEvent('fs-action', {
|
|
119
|
+
bubbles: true,
|
|
120
|
+
composed: true,
|
|
121
|
+
detail: {
|
|
122
|
+
action: this.action || 'button_click',
|
|
123
|
+
componentId: this.id,
|
|
124
|
+
}
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override render() {
|
|
129
|
+
const classes = `variant-${this.variant} size-${this.size}`;
|
|
130
|
+
|
|
131
|
+
return html`
|
|
132
|
+
<button
|
|
133
|
+
class=${classes}
|
|
134
|
+
?disabled=${this.disabled}
|
|
135
|
+
@click=${this.handleClick}
|
|
136
|
+
>
|
|
137
|
+
${this.label}
|
|
138
|
+
</button>
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
declare global {
|
|
144
|
+
interface HTMLElementTagNameMap {
|
|
145
|
+
'fs-button': FreesailButton;
|
|
146
|
+
}
|
|
147
|
+
}
|