@adia-ai/web-components 0.6.32 → 0.6.34
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/CHANGELOG.md +44 -0
- package/components/accordion/accordion.css +2 -2
- package/components/action-list/action-list.css +2 -2
- package/components/agent-artifact/agent-artifact.css +31 -31
- package/components/agent-feedback-bar/agent-feedback-bar.css +10 -10
- package/components/agent-questions/agent-questions.css +57 -57
- package/components/agent-reasoning/agent-reasoning.css +62 -62
- package/components/agent-suggestions/agent-suggestions.css +4 -4
- package/components/agent-trace/agent-trace.css +53 -53
- package/components/alert/alert.css +41 -41
- package/components/avatar/avatar.css +27 -27
- package/components/badge/badge.css +27 -27
- package/components/block/block.css +16 -16
- package/components/breadcrumb/breadcrumb.css +23 -23
- package/components/button/button.css +101 -91
- package/components/calendar-grid/calendar-grid.a2ui.json +136 -0
- package/components/calendar-grid/calendar-grid.css +226 -0
- package/components/calendar-grid/calendar-grid.d.ts +37 -0
- package/components/calendar-grid/calendar-grid.js +17 -0
- package/components/calendar-grid/calendar-grid.yaml +116 -0
- package/components/calendar-grid/class.js +300 -0
- package/components/calendar-picker/calendar-picker.css +139 -139
- package/components/canvas/canvas.css +12 -12
- package/components/card/card.css +83 -83
- package/components/chart/chart.css +224 -224
- package/components/chart-legend/chart-legend.css +26 -26
- package/components/check/check.css +40 -40
- package/components/code/code.css +125 -125
- package/components/col/col.css +15 -15
- package/components/color-picker/color-picker.css +55 -55
- package/components/combobox/class.js +861 -0
- package/components/combobox/combobox.a2ui.json +363 -0
- package/components/combobox/combobox.css +244 -0
- package/components/combobox/combobox.d.ts +113 -0
- package/components/combobox/combobox.examples.md +59 -0
- package/components/combobox/combobox.js +17 -0
- package/components/combobox/combobox.test.js +181 -0
- package/components/combobox/combobox.yaml +369 -0
- package/components/command/command.css +90 -90
- package/components/date-range-picker/class.js +775 -0
- package/components/date-range-picker/date-range-picker.a2ui.json +300 -0
- package/components/date-range-picker/date-range-picker.css +178 -0
- package/components/date-range-picker/date-range-picker.d.ts +82 -0
- package/components/date-range-picker/date-range-picker.examples.md +37 -0
- package/components/date-range-picker/date-range-picker.js +17 -0
- package/components/date-range-picker/date-range-picker.test.js +387 -0
- package/components/date-range-picker/date-range-picker.yaml +285 -0
- package/components/datetime-picker/class.js +706 -0
- package/components/datetime-picker/datetime-picker.a2ui.json +334 -0
- package/components/datetime-picker/datetime-picker.css +150 -0
- package/components/datetime-picker/datetime-picker.d.ts +86 -0
- package/components/datetime-picker/datetime-picker.examples.md +46 -0
- package/components/datetime-picker/datetime-picker.js +17 -0
- package/components/datetime-picker/datetime-picker.test.js +454 -0
- package/components/datetime-picker/datetime-picker.yaml +332 -0
- package/components/demo-toggle/demo-toggle.css +27 -27
- package/components/description-list/description-list.css +18 -18
- package/components/divider/divider.css +24 -24
- package/components/embed/embed.css +6 -6
- package/components/empty-state/empty-state.css +27 -27
- package/components/feed/feed.css +12 -12
- package/components/field/field.css +37 -28
- package/components/field/field.test.js +32 -0
- package/components/fields/fields.css +5 -5
- package/components/grid/grid.css +5 -5
- package/components/heatmap/heatmap.css +63 -63
- package/components/icon/icon.css +12 -12
- package/components/image/image.css +14 -14
- package/components/index.js +8 -0
- package/components/input/input.css +66 -66
- package/components/inspector/inspector.css +6 -6
- package/components/integration-card/class.js +410 -0
- package/components/integration-card/integration-card.a2ui.json +268 -0
- package/components/integration-card/integration-card.css +169 -0
- package/components/integration-card/integration-card.d.ts +63 -0
- package/components/integration-card/integration-card.examples.md +41 -0
- package/components/integration-card/integration-card.js +17 -0
- package/components/integration-card/integration-card.test.js +306 -0
- package/components/integration-card/integration-card.yaml +280 -0
- package/components/kbd/kbd.css +32 -32
- package/components/link/link.css +12 -12
- package/components/list/list.css +8 -8
- package/components/list-window/class.js +688 -0
- package/components/list-window/list-window.a2ui.json +277 -0
- package/components/list-window/list-window.css +124 -0
- package/components/list-window/list-window.d.ts +84 -0
- package/components/list-window/list-window.examples.md +73 -0
- package/components/list-window/list-window.js +17 -0
- package/components/list-window/list-window.test.js +303 -0
- package/components/list-window/list-window.yaml +270 -0
- package/components/menu/menu.css +8 -8
- package/components/modal/modal.css +43 -43
- package/components/nav/nav.css +40 -40
- package/components/nav-group/nav-group.css +52 -52
- package/components/nav-item/nav-item.css +44 -44
- package/components/noodles/noodles.css +31 -31
- package/components/option-card/option-card.css +69 -69
- package/components/otp-input/otp-input.css +30 -30
- package/components/page/page.css +18 -18
- package/components/pagination/pagination.css +61 -61
- package/components/pane/pane.css +57 -57
- package/components/pipeline-status/pipeline-status.css +65 -65
- package/components/popover/popover.css +17 -17
- package/components/progress/progress.css +23 -23
- package/components/progress-row/progress-row.css +17 -17
- package/components/radio/radio.css +39 -39
- package/components/range/range.css +55 -55
- package/components/rating/rating.css +28 -28
- package/components/richtext/richtext.css +133 -133
- package/components/row/row.css +19 -19
- package/components/search/search.css +5 -5
- package/components/segment/segment.css +24 -24
- package/components/segmented/segmented.css +25 -25
- package/components/select/select.css +84 -84
- package/components/skeleton/skeleton.css +14 -14
- package/components/slider/slider.css +46 -46
- package/components/spinner/class.js +69 -0
- package/components/spinner/spinner.a2ui.json +197 -0
- package/components/spinner/spinner.css +165 -0
- package/components/spinner/spinner.d.ts +26 -0
- package/components/spinner/spinner.examples.md +26 -0
- package/components/spinner/spinner.js +17 -0
- package/components/spinner/spinner.test.js +234 -0
- package/components/spinner/spinner.yaml +230 -0
- package/components/stack/stack.css +11 -11
- package/components/stat/stat.css +25 -25
- package/components/step-progress/step-progress.css +20 -20
- package/components/stepper/stepper.css +29 -29
- package/components/stream/stream.css +12 -12
- package/components/swatch/swatch.css +68 -68
- package/components/swiper/swiper.css +57 -57
- package/components/switch/switch.css +52 -52
- package/components/table/class.js +9 -0
- package/components/table/table.a2ui.json +1 -1
- package/components/table/table.css +162 -162
- package/components/table/table.d.ts +1 -1
- package/components/table/table.test.js +53 -0
- package/components/table/table.yaml +13 -1
- package/components/table-toolbar/table-toolbar.css +32 -32
- package/components/tabs/tabs.css +51 -51
- package/components/tag/tag.css +48 -48
- package/components/text/text.css +44 -44
- package/components/textarea/textarea.css +46 -46
- package/components/time-picker/class.js +693 -0
- package/components/time-picker/time-picker.a2ui.json +267 -0
- package/components/time-picker/time-picker.css +122 -0
- package/components/time-picker/time-picker.d.ts +75 -0
- package/components/time-picker/time-picker.examples.md +35 -0
- package/components/time-picker/time-picker.js +17 -0
- package/components/time-picker/time-picker.test.js +287 -0
- package/components/time-picker/time-picker.yaml +256 -0
- package/components/timeline/timeline.css +50 -50
- package/components/toast/toast.css +58 -58
- package/components/toggle-group/toggle-group.css +6 -6
- package/components/toggle-scheme/toggle-scheme.css +2 -2
- package/components/toolbar/toolbar.css +17 -17
- package/components/tooltip/tooltip.css +2 -2
- package/components/tree/tree.css +37 -37
- package/components/upload/upload.css +49 -49
- package/dist/icons-manifest.js +3 -3
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +121 -83
- package/package.json +1 -1
- package/styles/components.css +8 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
@scope (integration-card-ui) {
|
|
2
|
+
:where(:scope) {
|
|
3
|
+
/* ── Layout ── */
|
|
4
|
+
--integration-card-bg-default: var(--a-bg);
|
|
5
|
+
--integration-card-border-default: 1px solid var(--a-border-subtle);
|
|
6
|
+
--integration-card-radius-default: var(--a-radius-md);
|
|
7
|
+
--integration-card-px-default: var(--a-space-4);
|
|
8
|
+
--integration-card-py-default: var(--a-space-4);
|
|
9
|
+
--integration-card-gap-default: var(--a-space-3);
|
|
10
|
+
|
|
11
|
+
/* ── Inner rhythm ── */
|
|
12
|
+
--integration-card-row-gap-default: var(--a-space-3);
|
|
13
|
+
--integration-card-text-gap-default: var(--a-space-1);
|
|
14
|
+
|
|
15
|
+
/* ── Logo ── */
|
|
16
|
+
--integration-card-logo-size-default: var(--a-space-7);
|
|
17
|
+
--integration-card-logo-radius-default: var(--a-radius-sm);
|
|
18
|
+
|
|
19
|
+
/* ── Typography ── */
|
|
20
|
+
--integration-card-heading-fg-default: var(--a-fg-strong);
|
|
21
|
+
--integration-card-heading-size-default: var(--a-ui-size);
|
|
22
|
+
--integration-card-heading-weight-default: var(--a-weight-medium);
|
|
23
|
+
--integration-card-description-fg-default: var(--a-fg-muted);
|
|
24
|
+
--integration-card-description-size-default: var(--a-ui-sm);
|
|
25
|
+
--integration-card-error-fg-default: var(--a-danger-text);
|
|
26
|
+
--integration-card-error-size-default: var(--a-ui-sm);
|
|
27
|
+
|
|
28
|
+
/* ── States ── */
|
|
29
|
+
--integration-card-disabled-opacity-default: 0.6;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* ── Base — vertical stack: body row + (description) + (error) + footer ── */
|
|
33
|
+
:scope {
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
display: grid;
|
|
36
|
+
grid-template-rows: auto 1fr auto;
|
|
37
|
+
gap: var(--integration-card-gap, var(--integration-card-gap-default));
|
|
38
|
+
padding: var(--integration-card-py, var(--integration-card-py-default)) var(--integration-card-px, var(--integration-card-px-default));
|
|
39
|
+
background: var(--integration-card-bg, var(--integration-card-bg-default));
|
|
40
|
+
border: var(--integration-card-border, var(--integration-card-border-default));
|
|
41
|
+
border-radius: var(--integration-card-radius, var(--integration-card-radius-default));
|
|
42
|
+
width: stretch;
|
|
43
|
+
min-width: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* ── Status-driven border tints ── */
|
|
47
|
+
:scope[status="connected"] {
|
|
48
|
+
--integration-card-border-default: 1px solid var(--a-success-border, var(--a-success-strong));
|
|
49
|
+
}
|
|
50
|
+
:scope[status="error"] {
|
|
51
|
+
--integration-card-border-default: 1px solid var(--a-danger-border, var(--a-danger-strong));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ── Coming-soon + disabled → opacity reduction ── */
|
|
55
|
+
:scope[status="coming-soon"],
|
|
56
|
+
:scope[disabled] {
|
|
57
|
+
opacity: var(--integration-card-disabled-opacity, var(--integration-card-disabled-opacity-default));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ── Body row — logo | heading | status badge ── */
|
|
61
|
+
:scope > [data-integration-card-body] {
|
|
62
|
+
display: grid;
|
|
63
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
64
|
+
align-items: center;
|
|
65
|
+
column-gap: var(--integration-card-row-gap, var(--integration-card-row-gap-default));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ── Logo square ── */
|
|
69
|
+
:scope > [data-integration-card-body] > [data-integration-card-logo] {
|
|
70
|
+
grid-column: 1;
|
|
71
|
+
inline-size: var(--integration-card-logo-size, var(--integration-card-logo-size-default));
|
|
72
|
+
block-size: var(--integration-card-logo-size, var(--integration-card-logo-size-default));
|
|
73
|
+
border-radius: var(--integration-card-logo-radius, var(--integration-card-logo-radius-default));
|
|
74
|
+
display: inline-flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
flex-shrink: 0;
|
|
79
|
+
}
|
|
80
|
+
:scope > [data-integration-card-body] > [data-integration-card-logo][hidden] {
|
|
81
|
+
display: none;
|
|
82
|
+
}
|
|
83
|
+
:scope [data-integration-card-logo] > img {
|
|
84
|
+
inline-size: 100%;
|
|
85
|
+
block-size: 100%;
|
|
86
|
+
object-fit: contain;
|
|
87
|
+
display: block;
|
|
88
|
+
}
|
|
89
|
+
:scope [data-integration-card-logo] > icon-ui {
|
|
90
|
+
--a-icon-size: calc(var(--integration-card-logo-size, var(--integration-card-logo-size-default)) - var(--a-space-1));
|
|
91
|
+
color: var(--a-fg-muted);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ── Heading (provider name) ── */
|
|
95
|
+
:scope > [data-integration-card-body] > [data-integration-card-heading] {
|
|
96
|
+
grid-column: 2;
|
|
97
|
+
color: var(--integration-card-heading-fg, var(--integration-card-heading-fg-default));
|
|
98
|
+
font-size: var(--integration-card-heading-size, var(--integration-card-heading-size-default));
|
|
99
|
+
font-weight: var(--integration-card-heading-weight, var(--integration-card-heading-weight-default));
|
|
100
|
+
line-height: 1.3;
|
|
101
|
+
min-width: 0;
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
text-overflow: ellipsis;
|
|
104
|
+
white-space: nowrap;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* ── Status badge (sits in body row, right-aligned) ── */
|
|
108
|
+
:scope > [data-integration-card-body] > [data-integration-card-status] {
|
|
109
|
+
grid-column: 3;
|
|
110
|
+
justify-self: end;
|
|
111
|
+
}
|
|
112
|
+
:scope > [data-integration-card-body] > [data-integration-card-status][hidden] {
|
|
113
|
+
display: none;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ── Description ── */
|
|
117
|
+
:scope > [data-integration-card-description] {
|
|
118
|
+
margin: 0;
|
|
119
|
+
color: var(--integration-card-description-fg, var(--integration-card-description-fg-default));
|
|
120
|
+
font-size: var(--integration-card-description-size, var(--integration-card-description-size-default));
|
|
121
|
+
line-height: 1.4;
|
|
122
|
+
}
|
|
123
|
+
:scope > [data-integration-card-description][hidden] {
|
|
124
|
+
display: none;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ── Author-provided description (default slot) — same typography
|
|
128
|
+
by selector rather than slot to support bare <p>, <text-ui>, <small>. */
|
|
129
|
+
:scope > p:not([data-integration-card-description]):not([data-integration-card-error]),
|
|
130
|
+
:scope > small:not([data-integration-card-error]) {
|
|
131
|
+
margin: 0;
|
|
132
|
+
color: var(--integration-card-description-fg, var(--integration-card-description-fg-default));
|
|
133
|
+
font-size: var(--integration-card-description-size, var(--integration-card-description-size-default));
|
|
134
|
+
line-height: 1.4;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* ── Error message ── */
|
|
138
|
+
:scope > [data-integration-card-error] {
|
|
139
|
+
margin: 0;
|
|
140
|
+
color: var(--integration-card-error-fg, var(--integration-card-error-fg-default));
|
|
141
|
+
font-size: var(--integration-card-error-size, var(--integration-card-error-size-default));
|
|
142
|
+
line-height: 1.4;
|
|
143
|
+
}
|
|
144
|
+
:scope > [data-integration-card-error][hidden] {
|
|
145
|
+
display: none;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* ── Footer — action button + optional [slot="actions"] overflow ── */
|
|
149
|
+
:scope > [data-integration-card-footer] {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
gap: var(--integration-card-row-gap, var(--integration-card-row-gap-default));
|
|
153
|
+
margin: 0;
|
|
154
|
+
padding: 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* Action button is first child of footer; menu / overflow goes to the right. */
|
|
158
|
+
:scope > [data-integration-card-footer] > [data-integration-card-action] {
|
|
159
|
+
flex: 0 0 auto;
|
|
160
|
+
}
|
|
161
|
+
:scope > [data-integration-card-footer] > [slot="actions"] {
|
|
162
|
+
margin-inline-start: auto;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* ── Disabled state — button-ui already dims; ensure card chrome too. */
|
|
166
|
+
:scope[disabled] {
|
|
167
|
+
pointer-events: none;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<integration-card-ui>` — Single-tile primitive representing one third-party integration (Slack, GitHub, Stripe, …). Shows the provider logo + name + description + status pill + a single primary action button whose label and variant are derived from `status`. Composes into a grid via the SPEC-063 integrations-page composite. One tile = one provider; the button variant is computed from `status`, not a separate prop. Distinct from <option-card-ui> (a single-select radio with no status / async action) and <card-ui> (the generic bordered surface this specializes).
|
|
3
|
+
*
|
|
4
|
+
* @see https://ui-kit.exe.xyz/site/components/integration-card
|
|
5
|
+
*
|
|
6
|
+
* Type declarations generated by scripts/build/dts-codegen.mjs from
|
|
7
|
+
* the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
|
|
8
|
+
* run `npm run build:components`, then `npm run codegen:dts` to
|
|
9
|
+
* regenerate; or hand-author this file fully if rich event types are
|
|
10
|
+
* needed beyond what the yaml `events:` block can express.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { UIElement } from '../../core/element.js';
|
|
14
|
+
|
|
15
|
+
export interface IntegrationCardConfigureEventDetail {
|
|
16
|
+
/** The provider key for this card. */
|
|
17
|
+
provider: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type IntegrationCardConfigureEvent = CustomEvent<IntegrationCardConfigureEventDetail>;
|
|
21
|
+
export interface IntegrationCardConnectEventDetail {
|
|
22
|
+
/** The provider key for this card. */
|
|
23
|
+
provider: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type IntegrationCardConnectEvent = CustomEvent<IntegrationCardConnectEventDetail>;
|
|
27
|
+
export interface IntegrationCardDisconnectEventDetail {
|
|
28
|
+
/** The provider key for this card. */
|
|
29
|
+
provider: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type IntegrationCardDisconnectEvent = CustomEvent<IntegrationCardDisconnectEventDetail>;
|
|
33
|
+
export interface IntegrationCardRetryEventDetail {
|
|
34
|
+
/** The provider key for this card. */
|
|
35
|
+
provider: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type IntegrationCardRetryEvent = CustomEvent<IntegrationCardRetryEventDetail>;
|
|
39
|
+
|
|
40
|
+
export class UIIntegrationCard extends UIElement {
|
|
41
|
+
/** One-line description of what the integration does. */
|
|
42
|
+
description: string;
|
|
43
|
+
/** Disables the action button. */
|
|
44
|
+
disabled: boolean;
|
|
45
|
+
/** Logo URL (preferred — renders as <img>) or icon name (renders as <icon-ui>). URLs are sniffed by presence of `/`. */
|
|
46
|
+
logo: string;
|
|
47
|
+
/** Display name shown as the card title. Required. */
|
|
48
|
+
name: string;
|
|
49
|
+
/** Provider key — `slack`, `github`, etc. — used for analytics + a11y label. Required. */
|
|
50
|
+
provider: string;
|
|
51
|
+
/** Current connection state — drives the button label, variant, and visibility. `available` shows a primary `Connect`; `connected` shows an outline `Configure`; `error` shows an outline `Retry`; `pending` shows a non-interactive spinner; `coming-soon` hides the button. */
|
|
52
|
+
status: string;
|
|
53
|
+
|
|
54
|
+
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
55
|
+
type: K,
|
|
56
|
+
listener: (this: UIIntegrationCard, ev: HTMLElementEventMap[K]) => unknown,
|
|
57
|
+
options?: boolean | AddEventListenerOptions,
|
|
58
|
+
): void;
|
|
59
|
+
addEventListener(type: 'configure', listener: (ev: IntegrationCardConfigureEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
60
|
+
addEventListener(type: 'connect', listener: (ev: IntegrationCardConnectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
61
|
+
addEventListener(type: 'disconnect', listener: (ev: IntegrationCardDisconnectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
62
|
+
addEventListener(type: 'retry', listener: (ev: IntegrationCardRetryEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
63
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# integration-card — Examples
|
|
2
|
+
|
|
3
|
+
## Available (default)
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<integration-card-ui
|
|
7
|
+
provider="slack"
|
|
8
|
+
name="Slack"
|
|
9
|
+
logo="/integrations/slack.svg"
|
|
10
|
+
description="Send AI replies and notifications to channels."
|
|
11
|
+
status="available">
|
|
12
|
+
</integration-card-ui>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Connected — overflow menu
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<integration-card-ui provider="github" name="GitHub" status="connected"
|
|
19
|
+
logo="/integrations/github.svg"
|
|
20
|
+
description="Sync issues and pull requests.">
|
|
21
|
+
<menu-ui slot="actions" label="More" trigger-icon="dots-three" trigger-variant="ghost">
|
|
22
|
+
<menu-item-ui action="reauth">Re-authenticate</menu-item-ui>
|
|
23
|
+
<menu-item-ui action="disconnect" variant="danger">Disconnect</menu-item-ui>
|
|
24
|
+
</menu-ui>
|
|
25
|
+
</integration-card-ui>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Error — Retry
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<integration-card-ui provider="stripe" name="Stripe" status="error"
|
|
32
|
+
error-message="Token expired — re-authenticate.">
|
|
33
|
+
</integration-card-ui>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Coming soon (non-interactive)
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<integration-card-ui provider="zapier" name="Zapier" status="coming-soon">
|
|
40
|
+
</integration-card-ui>
|
|
41
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<integration-card-ui>` — auto-registers the tag on import.
|
|
3
|
+
*
|
|
4
|
+
* For non-side-effect class import (test isolation, tag override), use
|
|
5
|
+
* the `class` subpath:
|
|
6
|
+
*
|
|
7
|
+
* import { UIIntegrationCard } from '@adia-ai/web-components/components/integration-card/class';
|
|
8
|
+
*
|
|
9
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { defineIfFree } from '../../core/register.js';
|
|
13
|
+
import { UIIntegrationCard } from './class.js';
|
|
14
|
+
|
|
15
|
+
defineIfFree('integration-card-ui', UIIntegrationCard);
|
|
16
|
+
|
|
17
|
+
export { UIIntegrationCard };
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* integration-card-ui regression tests.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Default props / structural skeleton
|
|
6
|
+
* - Button label + variant computed from `status`
|
|
7
|
+
* - Coming-soon hides the button entirely
|
|
8
|
+
* - Each action click dispatches the typed event with `detail.provider`
|
|
9
|
+
* - `[disabled]` suppresses button activation
|
|
10
|
+
* - Error message visible only when status="error" AND error-message is set
|
|
11
|
+
* - Logo prop accepts URL (renders <img>) and icon name (renders <icon-ui>)
|
|
12
|
+
* - ARIA: role="group" + aria-labelledby pointing at the provider name
|
|
13
|
+
* - aria-pressed reflects connected vs available toggle semantics
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
17
|
+
import '../../core/element.js';
|
|
18
|
+
import '../button/button.js';
|
|
19
|
+
import '../badge/badge.js';
|
|
20
|
+
import '../icon/icon.js';
|
|
21
|
+
import './integration-card.js';
|
|
22
|
+
|
|
23
|
+
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
24
|
+
const tick2 = async () => { await tick(); await tick(); };
|
|
25
|
+
|
|
26
|
+
function mount(html) {
|
|
27
|
+
const wrap = document.createElement('div');
|
|
28
|
+
wrap.innerHTML = html;
|
|
29
|
+
document.body.appendChild(wrap);
|
|
30
|
+
return wrap.firstElementChild;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('integration-card-ui — defaults', () => {
|
|
34
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
35
|
+
|
|
36
|
+
it('defaults status to "available"', async () => {
|
|
37
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack"></integration-card-ui>');
|
|
38
|
+
await tick2();
|
|
39
|
+
// The reactive property accessor always returns the default; the
|
|
40
|
+
// attribute is reflected only on subsequent `set` calls (UIElement
|
|
41
|
+
// semantics). Both consumers + computed-button-label work off the
|
|
42
|
+
// property, not the attribute.
|
|
43
|
+
expect(c.status).toBe('available');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('exposes role="group" + aria-labelledby pointing at the heading', async () => {
|
|
47
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack"></integration-card-ui>');
|
|
48
|
+
await tick2();
|
|
49
|
+
expect(c.getAttribute('role')).toBe('group');
|
|
50
|
+
const labelledBy = c.getAttribute('aria-labelledby');
|
|
51
|
+
expect(labelledBy).toBeTruthy();
|
|
52
|
+
const heading = c.querySelector(`#${labelledBy}`);
|
|
53
|
+
expect(heading).not.toBeNull();
|
|
54
|
+
expect(heading.textContent).toBe('Slack');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('renders provider name into the heading', async () => {
|
|
58
|
+
const c = mount('<integration-card-ui provider="github" name="GitHub"></integration-card-ui>');
|
|
59
|
+
await tick2();
|
|
60
|
+
const heading = c.querySelector('[data-integration-card-heading]');
|
|
61
|
+
expect(heading).not.toBeNull();
|
|
62
|
+
expect(heading.textContent).toBe('GitHub');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('renders the description prop into the description element', async () => {
|
|
66
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack" description="Send messages."></integration-card-ui>');
|
|
67
|
+
await tick2();
|
|
68
|
+
const desc = c.querySelector('[data-integration-card-description]');
|
|
69
|
+
expect(desc).not.toBeNull();
|
|
70
|
+
expect(desc.textContent).toBe('Send messages.');
|
|
71
|
+
expect(desc.hidden).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('integration-card-ui — status drives button label + variant', () => {
|
|
76
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
77
|
+
|
|
78
|
+
it('status="available" → primary "Connect" button', async () => {
|
|
79
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack" status="available"></integration-card-ui>');
|
|
80
|
+
await tick2();
|
|
81
|
+
const btn = c.querySelector('[data-integration-card-action]');
|
|
82
|
+
expect(btn).not.toBeNull();
|
|
83
|
+
expect(btn.getAttribute('text')).toBe('Connect');
|
|
84
|
+
expect(btn.getAttribute('variant')).toBe('primary');
|
|
85
|
+
expect(btn.getAttribute('aria-pressed')).toBe('false');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('status="connected" → outline "Configure" button with aria-pressed="true"', async () => {
|
|
89
|
+
const c = mount('<integration-card-ui provider="github" name="GitHub" status="connected"></integration-card-ui>');
|
|
90
|
+
await tick2();
|
|
91
|
+
const btn = c.querySelector('[data-integration-card-action]');
|
|
92
|
+
expect(btn).not.toBeNull();
|
|
93
|
+
expect(btn.getAttribute('text')).toBe('Configure');
|
|
94
|
+
expect(btn.getAttribute('variant')).toBe('outline');
|
|
95
|
+
expect(btn.getAttribute('aria-pressed')).toBe('true');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('status="error" → outline "Retry" button', async () => {
|
|
99
|
+
const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="error"></integration-card-ui>');
|
|
100
|
+
await tick2();
|
|
101
|
+
const btn = c.querySelector('[data-integration-card-action]');
|
|
102
|
+
expect(btn).not.toBeNull();
|
|
103
|
+
expect(btn.getAttribute('text')).toBe('Retry');
|
|
104
|
+
expect(btn.getAttribute('variant')).toBe('outline');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('status="pending" → button shows pending label and is disabled', async () => {
|
|
108
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack" status="pending"></integration-card-ui>');
|
|
109
|
+
await tick2();
|
|
110
|
+
const btn = c.querySelector('[data-integration-card-action]');
|
|
111
|
+
expect(btn).not.toBeNull();
|
|
112
|
+
expect(btn.hasAttribute('disabled')).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('status="coming-soon" → button is removed', async () => {
|
|
116
|
+
const c = mount('<integration-card-ui provider="zapier" name="Zapier" status="coming-soon"></integration-card-ui>');
|
|
117
|
+
await tick2();
|
|
118
|
+
const btn = c.querySelector('[data-integration-card-action]');
|
|
119
|
+
expect(btn).toBeNull();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('integration-card-ui — status badge', () => {
|
|
124
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
125
|
+
|
|
126
|
+
it('status="connected" → success badge', async () => {
|
|
127
|
+
const c = mount('<integration-card-ui provider="github" name="GitHub" status="connected"></integration-card-ui>');
|
|
128
|
+
await tick2();
|
|
129
|
+
const badge = c.querySelector('[data-integration-card-status]');
|
|
130
|
+
expect(badge).not.toBeNull();
|
|
131
|
+
expect(badge.hidden).toBe(false);
|
|
132
|
+
expect(badge.getAttribute('variant')).toBe('success');
|
|
133
|
+
expect(badge.getAttribute('text')).toBe('Connected');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('status="error" → danger badge', async () => {
|
|
137
|
+
const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="error"></integration-card-ui>');
|
|
138
|
+
await tick2();
|
|
139
|
+
const badge = c.querySelector('[data-integration-card-status]');
|
|
140
|
+
expect(badge.hidden).toBe(false);
|
|
141
|
+
expect(badge.getAttribute('variant')).toBe('danger');
|
|
142
|
+
expect(badge.getAttribute('text')).toBe('Error');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('status="available" → no visible status badge', async () => {
|
|
146
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack" status="available"></integration-card-ui>');
|
|
147
|
+
await tick2();
|
|
148
|
+
const badge = c.querySelector('[data-integration-card-status]');
|
|
149
|
+
expect(badge.hidden).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('integration-card-ui — events', () => {
|
|
154
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
155
|
+
|
|
156
|
+
it('dispatches "connect" event with provider when available button is pressed', async () => {
|
|
157
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack" status="available"></integration-card-ui>');
|
|
158
|
+
await tick2();
|
|
159
|
+
let received = null;
|
|
160
|
+
c.addEventListener('connect', (e) => { received = e.detail; });
|
|
161
|
+
const btn = c.querySelector('[data-integration-card-action]');
|
|
162
|
+
btn.dispatchEvent(new Event('press', { bubbles: true }));
|
|
163
|
+
expect(received).toEqual({ provider: 'slack' });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('dispatches "configure" event when connected button is pressed', async () => {
|
|
167
|
+
const c = mount('<integration-card-ui provider="github" name="GitHub" status="connected"></integration-card-ui>');
|
|
168
|
+
await tick2();
|
|
169
|
+
let received = null;
|
|
170
|
+
c.addEventListener('configure', (e) => { received = e.detail; });
|
|
171
|
+
const btn = c.querySelector('[data-integration-card-action]');
|
|
172
|
+
btn.dispatchEvent(new Event('press', { bubbles: true }));
|
|
173
|
+
expect(received).toEqual({ provider: 'github' });
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('dispatches "retry" event when error button is pressed', async () => {
|
|
177
|
+
const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="error"></integration-card-ui>');
|
|
178
|
+
await tick2();
|
|
179
|
+
let received = null;
|
|
180
|
+
c.addEventListener('retry', (e) => { received = e.detail; });
|
|
181
|
+
const btn = c.querySelector('[data-integration-card-action]');
|
|
182
|
+
btn.dispatchEvent(new Event('press', { bubbles: true }));
|
|
183
|
+
expect(received).toEqual({ provider: 'stripe' });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('[disabled] suppresses button activation', async () => {
|
|
187
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack" status="available" disabled></integration-card-ui>');
|
|
188
|
+
await tick2();
|
|
189
|
+
let received = null;
|
|
190
|
+
c.addEventListener('connect', (e) => { received = e.detail; });
|
|
191
|
+
const btn = c.querySelector('[data-integration-card-action]');
|
|
192
|
+
expect(btn.hasAttribute('disabled')).toBe(true);
|
|
193
|
+
btn.dispatchEvent(new Event('press', { bubbles: true }));
|
|
194
|
+
expect(received).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('pending status does not dispatch any event on press', async () => {
|
|
198
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack" status="pending"></integration-card-ui>');
|
|
199
|
+
await tick2();
|
|
200
|
+
let received = null;
|
|
201
|
+
c.addEventListener('connect', (e) => { received = e.detail; });
|
|
202
|
+
c.addEventListener('configure', (e) => { received = e.detail; });
|
|
203
|
+
c.addEventListener('retry', (e) => { received = e.detail; });
|
|
204
|
+
const btn = c.querySelector('[data-integration-card-action]');
|
|
205
|
+
btn.dispatchEvent(new Event('press', { bubbles: true }));
|
|
206
|
+
expect(received).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('integration-card-ui — error message', () => {
|
|
211
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
212
|
+
|
|
213
|
+
it('renders error-message when status="error"', async () => {
|
|
214
|
+
const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="error" error-message="Token expired"></integration-card-ui>');
|
|
215
|
+
await tick2();
|
|
216
|
+
const err = c.querySelector('[data-integration-card-error]');
|
|
217
|
+
expect(err).not.toBeNull();
|
|
218
|
+
expect(err.hidden).toBe(false);
|
|
219
|
+
expect(err.textContent).toBe('Token expired');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('hides error message when status is not error', async () => {
|
|
223
|
+
const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="available" error-message="Stale"></integration-card-ui>');
|
|
224
|
+
await tick2();
|
|
225
|
+
const err = c.querySelector('[data-integration-card-error]');
|
|
226
|
+
if (err) expect(err.hidden).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('does not render error block when status="error" but error-message empty', async () => {
|
|
230
|
+
const c = mount('<integration-card-ui provider="stripe" name="Stripe" status="error"></integration-card-ui>');
|
|
231
|
+
await tick2();
|
|
232
|
+
const err = c.querySelector('[data-integration-card-error]');
|
|
233
|
+
// Either the element is absent or hidden.
|
|
234
|
+
if (err) expect(err.hidden).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('integration-card-ui — logo rendering', () => {
|
|
239
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
240
|
+
|
|
241
|
+
it('renders <img> when logo prop contains a "/"', async () => {
|
|
242
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack" logo="/integrations/slack.svg"></integration-card-ui>');
|
|
243
|
+
await tick2();
|
|
244
|
+
const img = c.querySelector('[data-integration-card-logo] img');
|
|
245
|
+
expect(img).not.toBeNull();
|
|
246
|
+
expect(img.getAttribute('src')).toBe('/integrations/slack.svg');
|
|
247
|
+
expect(img.getAttribute('alt')).toContain('Slack');
|
|
248
|
+
expect(img.hasAttribute('data-integration-logo')).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('renders <icon-ui> when logo prop is a bare icon name', async () => {
|
|
252
|
+
const c = mount('<integration-card-ui provider="zap" name="Zap" logo="lightning"></integration-card-ui>');
|
|
253
|
+
await tick2();
|
|
254
|
+
const icon = c.querySelector('[data-integration-card-logo] icon-ui');
|
|
255
|
+
expect(icon).not.toBeNull();
|
|
256
|
+
expect(icon.getAttribute('name')).toBe('lightning');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('hides logo wrapper when logo prop is empty', async () => {
|
|
260
|
+
const c = mount('<integration-card-ui provider="slack" name="Slack"></integration-card-ui>');
|
|
261
|
+
await tick2();
|
|
262
|
+
const wrap = c.querySelector('[data-integration-card-logo]');
|
|
263
|
+
expect(wrap).not.toBeNull();
|
|
264
|
+
expect(wrap.hidden).toBe(true);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe('integration-card-ui — CSS contract (source-grep)', () => {
|
|
269
|
+
it('uses two-block @scope pattern (token block + base block)', async () => {
|
|
270
|
+
const { readFileSync } = await import('node:fs');
|
|
271
|
+
const { fileURLToPath } = await import('node:url');
|
|
272
|
+
const { dirname, resolve } = await import('node:path');
|
|
273
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
274
|
+
const CSS = readFileSync(resolve(HERE, 'integration-card.css'), 'utf8');
|
|
275
|
+
|
|
276
|
+
expect(CSS).toMatch(/@scope\s*\(\s*integration-card-ui\s*\)/);
|
|
277
|
+
expect(CSS).toMatch(/:where\(:scope\)\s*\{/);
|
|
278
|
+
expect(CSS).toMatch(/--integration-card-bg:/);
|
|
279
|
+
expect(CSS).toMatch(/--integration-card-border:/);
|
|
280
|
+
expect(CSS).toMatch(/--integration-card-radius:/);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('keeps status-driven border tint (status="connected") inside @scope', async () => {
|
|
284
|
+
const { readFileSync } = await import('node:fs');
|
|
285
|
+
const { fileURLToPath } = await import('node:url');
|
|
286
|
+
const { dirname, resolve } = await import('node:path');
|
|
287
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
288
|
+
const CSS = readFileSync(resolve(HERE, 'integration-card.css'), 'utf8');
|
|
289
|
+
expect(CSS).toMatch(/:scope\[status="connected"\]\s*\{[^}]*--integration-card-border/);
|
|
290
|
+
expect(CSS).toMatch(/:scope\[status="error"\]\s*\{[^}]*--integration-card-border/);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('does NOT carry raw hex / rgb / oklch values', async () => {
|
|
294
|
+
const { readFileSync } = await import('node:fs');
|
|
295
|
+
const { fileURLToPath } = await import('node:url');
|
|
296
|
+
const { dirname, resolve } = await import('node:path');
|
|
297
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
298
|
+
const CSS = readFileSync(resolve(HERE, 'integration-card.css'), 'utf8');
|
|
299
|
+
// No #abc / #abcdef
|
|
300
|
+
expect(CSS).not.toMatch(/#[0-9a-fA-F]{3,8}\b/);
|
|
301
|
+
// No rgb()/rgba()
|
|
302
|
+
expect(CSS).not.toMatch(/\brgba?\s*\(/);
|
|
303
|
+
// No raw oklch()
|
|
304
|
+
expect(CSS).not.toMatch(/\boklch\s*\(/);
|
|
305
|
+
});
|
|
306
|
+
});
|