@descope-ui/descope-outbound-apps 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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
+
5
+ ## 0.0.1 (2025-07-21)
6
+
7
+ ### Dependency Updates
8
+
9
+ * `e2e-utils` updated to version `0.0.1`
10
+ * `@descope-ui/common` updated to version `0.0.18`
11
+ * `@descope-ui/theme-globals` updated to version `0.0.19`
12
+ * `@descope-ui/descope-list` updated to version `0.0.1`
13
+ * `@descope-ui/descope-list-item` updated to version `0.0.1`
14
+ * `@descope-ui/descope-text` updated to version `0.0.19`
15
+ * `@descope-ui/descope-avatar` updated to version `0.0.19`
16
+ * `@descope-ui/descope-icon` updated to version `0.0.17`
17
+ * `@descope-ui/descope-button` updated to version `0.0.18`
18
+ # Changelog
@@ -0,0 +1,82 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { getStoryUrl, loopConfig } from 'e2e-utils';
3
+
4
+ const componentAttributes = {
5
+ direction: ['ltr', 'rtl'],
6
+ numberOfItems: ['1', '5', '10'],
7
+ size: ['xs', 'sm', 'md', 'lg'],
8
+ 'connect-button-label': ['', 'My Connect'],
9
+ 'disconnect-button-label': ['', 'My Disconnect']
10
+ };
11
+
12
+ const storyName = 'descope-outbound-apps';
13
+ const componentName = 'descope-outbound-apps';
14
+
15
+ test.describe('theme', () => {
16
+ loopConfig(componentAttributes, (attr, value) => {
17
+ test.describe(`${attr}: ${value}`, () => {
18
+ test.beforeEach(async ({ page }) => {
19
+ await page.goto(getStoryUrl(storyName, { [attr]: value }), { waitUntil: 'networkidle' });
20
+ });
21
+
22
+ test('style', async ({ page }) => {
23
+ const componentParent = page.locator(componentName);
24
+ await page.waitForTimeout(2000);
25
+
26
+ expect(await componentParent.screenshot()).toMatchSnapshot();
27
+ });
28
+ });
29
+ });
30
+ });
31
+
32
+ const setupEventListener = (eventName: string) => {
33
+ return `
34
+ window.eventDetails = [];
35
+ document.addEventListener('${eventName}', (event) => {
36
+ window.eventDetails.push(event.detail);
37
+ });
38
+ `;
39
+ };
40
+
41
+ test.describe('logic', () => {
42
+ test('dispatch connect action with appId', async ({ page }) => {
43
+ await page.goto(getStoryUrl(storyName), {
44
+ waitUntil: 'networkidle',
45
+ });
46
+
47
+ await page.addInitScript(setupEventListener('connect-clicked'));
48
+
49
+ await page.reload({ waitUntil: 'networkidle' });
50
+
51
+ await page.getByText('Connect').first().click();
52
+ await page.waitForTimeout(500);
53
+
54
+ const eventDetail = await page.evaluate(() => (window as any).eventDetails[0]);
55
+
56
+ expect(eventDetail).toBeDefined();
57
+ expect(eventDetail).toHaveProperty('action', 'connect');
58
+ expect(eventDetail).toHaveProperty('id', 'appId_1');
59
+ });
60
+
61
+ test('dispatch disconnect action with appId', async ({ page }) => {
62
+ await page.goto(getStoryUrl(storyName), {
63
+ waitUntil: 'networkidle',
64
+ });
65
+
66
+ await page.addInitScript(setupEventListener('disconnect-clicked'));
67
+
68
+ await page.reload({ waitUntil: 'networkidle' });
69
+
70
+ await page.getByText('Disconnect').first().click();
71
+ await page.waitForTimeout(500);
72
+
73
+ await page.getByText('Disconnect').first().click();
74
+ await page.waitForTimeout(500);
75
+
76
+ const eventDetail = await page.evaluate(() => (window as any).eventDetails[0]);
77
+
78
+ expect(eventDetail).toBeDefined();
79
+ expect(eventDetail).toHaveProperty('action', 'disconnect');
80
+ expect(eventDetail).toHaveProperty('id', 'appId_2');
81
+ });
82
+ });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@descope-ui/descope-outbound-apps",
3
+ "version": "0.0.1",
4
+ "exports": {
5
+ ".": {
6
+ "import": "./src/component/index.js"
7
+ },
8
+ "./theme": {
9
+ "import": "./src/theme.js"
10
+ },
11
+ "./class": {
12
+ "import": "./src/component/OutboundAppsClass.js"
13
+ }
14
+ },
15
+ "devDependencies": {
16
+ "@playwright/test": "1.38.1",
17
+ "e2e-utils": "0.0.1"
18
+ },
19
+ "dependencies": {
20
+ "@descope-ui/common": "0.0.18",
21
+ "@descope-ui/theme-globals": "0.0.19",
22
+ "@descope-ui/descope-list": "0.0.1",
23
+ "@descope-ui/descope-list-item": "0.0.1",
24
+ "@descope-ui/descope-text": "0.0.19",
25
+ "@descope-ui/descope-avatar": "0.0.19",
26
+ "@descope-ui/descope-icon": "0.0.17",
27
+ "@descope-ui/descope-button": "0.0.18"
28
+ },
29
+ "publishConfig": {
30
+ "link-workspace-packages": false
31
+ },
32
+ "scripts": {
33
+ "test": "echo 'No tests defined' && exit 0",
34
+ "test:e2e": "echo 'No e2e tests defined' && exit 0"
35
+ }
36
+ }
package/project.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@descope-ui/descope-outbound-apps",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/web-components/components/descope-outbound-apps/src",
5
+ "projectType": "library",
6
+ "targets": {
7
+ "version": {
8
+ "executor": "@jscutlery/semver:version",
9
+ "options": {
10
+ "trackDeps": true,
11
+ "push": false,
12
+ "preset": "conventional"
13
+ }
14
+ }
15
+ },
16
+ "tags": []
17
+ }
@@ -0,0 +1,213 @@
1
+ import { compose } from '@descope-ui/common/utils';
2
+ import { limitAbbreviation, getComponentName, injectStyle } from '@descope-ui/common/components-helpers';
3
+ import {
4
+ createStyleMixin,
5
+ draggableMixin,
6
+ componentNameValidationMixin,
7
+ createDynamicDataMixin,
8
+ } from '@descope-ui/common/components-mixins';
9
+ import { createBaseClass } from '@descope-ui/common/base-classes';
10
+ import { IconClass } from '@descope-ui/descope-icon/class';
11
+ import { TextClass } from '@descope-ui/descope-text/class';
12
+ import { AvatarClass } from '@descope-ui/descope-avatar/class';
13
+ import { ListClass } from '@descope-ui/descope-list/class';
14
+ import { ListItemClass } from '@descope-ui/descope-list-item/class';
15
+
16
+ export const componentName = getComponentName('outbound-apps');
17
+
18
+ export const observedAttributes = ['connect-button-label', 'disconnect-button-label', 'data'];
19
+
20
+ const itemRenderer = (
21
+ { name, description, logo, appId, isConnected },
22
+ _,
23
+ ref,
24
+ ) => {
25
+ const action = isConnected ? 'disconnect' : 'connect';
26
+ return `
27
+ <descope-list-item>
28
+ <descope-avatar
29
+ ${logo ? `img="${logo}"` : ''}
30
+ ${name ? `display-name="${name}" abbr=${limitAbbreviation(name)}` : ''}
31
+ size=${ref.size}
32
+ class="app-logo"
33
+ ></descope-avatar>
34
+ <div class="content">
35
+ <div class="app-title">
36
+ <descope-text
37
+ variant="subtitle1"
38
+ mode="primary"
39
+ >${name}</descope-text>
40
+ </div>
41
+ ${
42
+ description
43
+ ? `
44
+ <div class="app-description">
45
+ <descope-text
46
+ variant="body2"
47
+ mode="primary"
48
+ >${description}</descope-text>
49
+ </div>
50
+ `
51
+ : ''
52
+ }
53
+ </div>
54
+ <div class="controls">
55
+ <descope-button variant="link" mode="primary" size=${ref.size} data-action="${action}" data-outbound-app-id="${appId}" data-id="${action}">
56
+ ${isConnected ? ref.disconnectButtonLabel : ref.connectButtonLabel }
57
+ </descope-button>
58
+ </div>
59
+ </descope-list-item>
60
+ `
61
+ };
62
+
63
+ const BaseClass = createBaseClass({
64
+ componentName,
65
+ baseSelector: 'descope-list',
66
+ });
67
+
68
+ class RawOutboundAppsClass extends BaseClass {
69
+ static get observedAttributes() {
70
+ return observedAttributes;
71
+ }
72
+
73
+ constructor() {
74
+ super();
75
+
76
+ this.attachShadow({ mode: 'open' }).innerHTML = `
77
+ <descope-list></descope-list>
78
+ `;
79
+
80
+ this.appsList = this.shadowRoot.querySelector('descope-list');
81
+
82
+ injectStyle(
83
+ `
84
+ :host {
85
+ width: 100%;
86
+ }
87
+ .controls {
88
+ display: flex;
89
+ min-width: 8em;
90
+ justify-content: end;
91
+ }
92
+ .content {
93
+ flex-grow: 1;
94
+ }
95
+ descope-list {
96
+ display: block;
97
+ }
98
+ `,
99
+ this
100
+ );
101
+ }
102
+
103
+ init() {
104
+ super.init?.();
105
+
106
+ this.appsList.itemRenderer = itemRenderer;
107
+
108
+ this.appsList.addEventListener('click', this.onAppsListClick.bind(this));
109
+ }
110
+
111
+ onAppsListClick(e) {
112
+ const id = e.srcElement.getAttribute('data-outbound-app-id');
113
+ const action = e.srcElement.getAttribute('data-action');
114
+ if (id && action) {
115
+ this.dispatchEvent(new CustomEvent(`${action}-clicked`, { bubbles: true, detail: { id, action } }));
116
+ }
117
+ }
118
+
119
+ get size() {
120
+ return this.getAttribute('size') || 'sm';
121
+ }
122
+
123
+ get connectButtonLabel() {
124
+ return this.getAttribute('connect-button-label') || 'Connect';
125
+ }
126
+
127
+ get disconnectButtonLabel() {
128
+ return this.getAttribute('disconnect-button-label') || 'Disconnect';
129
+ }
130
+ }
131
+
132
+ const { host } = {
133
+ host: { selector: () => ':host' },
134
+ };
135
+
136
+ export const OutboundAppsClass = compose(
137
+ createStyleMixin({
138
+ mappings: {
139
+ hostWidth: { ...host, property: 'width' },
140
+ minHeight: { selector: () => ':host' },
141
+ hostDirection: [
142
+ { ...host, property: 'direction' },
143
+ {
144
+ selector: 'descope-list',
145
+ property: 'direction'
146
+ },
147
+ {
148
+ selector: 'descope-list-item',
149
+ property: 'direction'
150
+ },
151
+ ],
152
+ iconColor: { selector: () => ' descope-icon', property: IconClass.cssVarList.fill },
153
+ errorIconColor: { selector: () => ' descope-icon.error-icon', property: IconClass.cssVarList.fill },
154
+ fontSize: {
155
+ selector: TextClass.componentName,
156
+ property: TextClass.cssVarList.fontSize,
157
+ },
158
+ appLogoBackgroundColor: {
159
+ selector: AvatarClass.componentName,
160
+ property: AvatarClass.cssVarList.avatarBackgroundColor,
161
+ },
162
+ appLogoGap: {
163
+ selector: () => ' .app-logo',
164
+ property: 'margin-inline-end'
165
+ },
166
+ itemsTextAlign: {
167
+ selector: TextClass.componentName,
168
+ property: TextClass.cssVarList.textAlign,
169
+ },
170
+ itemCursor: {
171
+ selector: ListItemClass.componentName,
172
+ property: ListItemClass.cssVarList.cursor
173
+ },
174
+ itemOutline: {
175
+ selector: ListItemClass.componentName,
176
+ property: ListItemClass.cssVarList.outline
177
+ },
178
+ itemBorderColor: {
179
+ selector: ListItemClass.componentName,
180
+ property: ListItemClass.cssVarList.borderColor
181
+ },
182
+ itemBackgroundColor: {
183
+ selector: ListItemClass.componentName,
184
+ property: ListItemClass.cssVarList.backgroundColor
185
+ },
186
+ listBorderWidth: {
187
+ selector: () => ListClass.componentName,
188
+ property: ListClass.cssVarList.borderWidth
189
+ },
190
+ listBoxShadow: {
191
+ selector: () => ListClass.componentName,
192
+ property: ListClass.cssVarList.boxShadow
193
+ },
194
+ listPadding: [
195
+ {
196
+ selector: () => ListClass.componentName,
197
+ property: ListClass.cssVarList.verticalPadding
198
+ },
199
+ {
200
+ selector: () => ListClass.componentName,
201
+ property: ListClass.cssVarList.horizontalPadding
202
+ }
203
+ ]
204
+ },
205
+ }),
206
+ draggableMixin,
207
+ createDynamicDataMixin({ itemRenderer, rerenderAttrsList: [
208
+ 'size',
209
+ 'connect-button-label',
210
+ 'disconnect-button-label'
211
+ ]}),
212
+ componentNameValidationMixin,
213
+ )(RawOutboundAppsClass);
@@ -0,0 +1,10 @@
1
+ import '@descope-ui/descope-list';
2
+ import '@descope-ui/descope-list-item';
3
+ import '@descope-ui/descope-text';
4
+ import '@descope-ui/descope-avatar';
5
+ import '@descope-ui/descope-button';
6
+ import { componentName, OutboundAppsClass } from './OutboundAppsClass';
7
+
8
+ customElements.define(componentName, OutboundAppsClass);
9
+
10
+ export { OutboundAppsClass, componentName };
package/src/theme.js ADDED
@@ -0,0 +1,38 @@
1
+ import { OutboundAppsClass } from './component/OutboundAppsClass';
2
+ import globals from '@descope-ui/theme-globals';
3
+
4
+ export const vars = OutboundAppsClass.cssVarList;
5
+
6
+ const outboundApps = {
7
+ [vars.iconColor]: globals.colors.primary.main,
8
+ [vars.errorIconColor]: globals.colors.error.main,
9
+
10
+ [vars.appLogoBackgroundColor]: 'none',
11
+ [vars.appLogoGap]: globals.spacing.md,
12
+
13
+ // list-item overrides
14
+ [vars.itemCursor]: 'default',
15
+ [vars.itemOutline]: 'none',
16
+ [vars.itemBorderColor]: 'transparent',
17
+
18
+ [vars.listPadding]: '0',
19
+ [vars.listBorderWidth]: '0',
20
+ [vars.listBoxShadow]: 'none',
21
+
22
+ size: {
23
+ xs: {
24
+ [vars.fontSize]: '0.6em',
25
+ },
26
+ sm: {
27
+ [vars.fontSize]: '0.8em',
28
+ },
29
+ md: {
30
+ [vars.fontSize]: '1em',
31
+ },
32
+ lg: {
33
+ [vars.fontSize]: '1.5em',
34
+ },
35
+ },
36
+ };
37
+
38
+ export default outboundApps;
@@ -0,0 +1,65 @@
1
+ import { componentName } from '../src/component';
2
+ import { directionControl, sizeControl } from '@descope-ui/common/sb-controls';
3
+ import appIcon from './google.svg';
4
+
5
+ const Template = ({ direction, size, 'connect-button-label': connectButtonLabel, 'disconnect-button-label': disconnectButtonLabel }) => `
6
+ <descope-outbound-apps
7
+ st-host-direction="${direction || ''}"
8
+ size=${size || ''}
9
+ connect-button-label="${connectButtonLabel || ''}"
10
+ disconnect-button-label="${disconnectButtonLabel || ''}"
11
+ >
12
+ </descope-outbound-apps>
13
+ <div class="test-output"></div>
14
+ `;
15
+
16
+ export default {
17
+ component: componentName,
18
+ title: 'descope-outbound-apps',
19
+ decorators: [
20
+ (story, { args }) => {
21
+ setTimeout(() => {
22
+ const comp = document.querySelector('descope-outbound-apps');
23
+
24
+ comp.data = Array.from(
25
+ { length: Number(args.numberOfItems) },
26
+ (_, i) => ({
27
+ appId: `appId_${i + 1}`,
28
+ name: `App ${i + 1}`,
29
+ logo: appIcon,
30
+ description: 'App description',
31
+ isConnected: !!(i % 2),
32
+ }),
33
+ );
34
+ });
35
+ return story();
36
+ },
37
+ (story) => {
38
+ setTimeout(() => {
39
+ const component = document.querySelector('descope-outbound-apps');
40
+ component.addEventListener('outbound-app-action', (e) =>
41
+ console.log('outbound-app-action', e.detail),
42
+ );
43
+ });
44
+ return story();
45
+ },
46
+ ],
47
+ argTypes: {
48
+ ...directionControl,
49
+ ...sizeControl,
50
+ 'connect-button-label': {
51
+ control: { type: 'text' },
52
+ },
53
+ 'disconnect-button-label': {
54
+ control: { type: 'text' },
55
+ },
56
+ },
57
+ };
58
+
59
+ export const Default = Template.bind({});
60
+
61
+ Default.args = {
62
+ size: 'sm',
63
+ numberOfItems: '5',
64
+ emptyState: 'No items in the list...',
65
+ };
@@ -0,0 +1,25 @@
1
+ <svg
2
+ width="1.5em"
3
+ height="1.5em"
4
+ viewBox="0 0 20 20"
5
+ xmlns="http://www.w3.org/2000/svg"
6
+ data-icon="google"
7
+ >
8
+ <path
9
+ d="m19.6 10.2274c0-.70912-.0636-1.39092-.1818-2.04552h-9.4182v3.86822h5.3818c-.2318 1.25-.9363 2.3091-1.9954 3.0182v2.5091h3.2318c1.8909-1.7409 2.9818-4.3046 2.9818-7.35z"
10
+ fill="#4285f4"
11
+ />
12
+ <path
13
+ d="m10 19.9999c2.7 0 4.9636-.8955 6.6181-2.4227l-3.2318-2.5091c-.8954.6-2.0409.9545-3.3863.9545-2.6046 0-4.8091-1.7591-5.5955-4.1227h-3.3409v2.5909c1.6455 3.2682 5.0273 5.5091 8.9364 5.5091z"
14
+ fill="#34a853"
15
+ />
16
+ <path
17
+ d="m4.4045 11.8999c-.2-.6-.3136-1.2409-.3136-1.89997 0-.6591.1136-1.3.3136-1.9v-2.5909h-3.3409c-.6772 1.35-1.0636 2.8773-1.0636 4.4909 0 1.61357.3864 3.14087 1.0636 4.49087z"
18
+ fill="#fbbc04"
19
+ />
20
+ <path
21
+ d="m10 3.9773c1.4681 0 2.7863.5045 3.8227 1.4954l2.8682-2.8682c-1.7318-1.6136-3.9955-2.6045-6.6909-2.6045-3.9091 0-7.2909 2.2409-8.9364 5.5091l3.3409 2.5909c.7864-2.3636 2.9909-4.1227 5.5955-4.1227z"
22
+ fill="#e94235"
23
+ />
24
+ <script>alert(1)</script>
25
+ </svg>