@descope-ui/descope-recovery-codes 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,14 @@
1
+ # Changelog
2
+
3
+ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
+
5
+ ## 0.0.1 (2025-06-29)
6
+
7
+ ### Dependency Updates
8
+
9
+ * `e2e-utils` updated to version `0.0.1`
10
+ * `test-drivers` updated to version `0.0.1`
11
+ * `@descope-ui/common` updated to version `0.0.15`
12
+ * `@descope-ui/theme-globals` updated to version `0.0.15`
13
+ * `@descope-ui/descope-text` updated to version `0.0.15`
14
+ # Changelog
@@ -0,0 +1,71 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { getStoryUrl, loopConfig, loopPresets } from 'e2e-utils';
3
+ import createRecoveryCodesTestDriver from '../testDriver/recoveryCodesTestDriver';
4
+
5
+ const componentAttributes = {
6
+ variant: ['h1', 'h2', 'h3', 'subtitle1', 'subtitle2', 'body1', 'body2'],
7
+ mode: ['primary', 'secondary', 'success', 'error'],
8
+ 'text-align': ['left', 'center', 'right'],
9
+ 'full-width': ['false', 'true'],
10
+ itemsSource: ['attr', 'prop'],
11
+ };
12
+
13
+ const storyName = 'descope-recovery-codes';
14
+ const componentName = 'descope-recovery-codes';
15
+
16
+ test.describe('theme', () => {
17
+ loopConfig(componentAttributes, (attr, value) => {
18
+ test(`${attr}: ${value}`, async ({ page }) => {
19
+ await page.goto(getStoryUrl(storyName, { [attr]: value }));
20
+ const component = page.locator(componentName);
21
+ expect(await component.screenshot()).toMatchSnapshot();
22
+ });
23
+ });
24
+
25
+ test(`direction: rtl`, async ({ page }) => {
26
+ await page.goto(
27
+ getStoryUrl(storyName, { direction: 'rtl', text: '-Hello World' }),
28
+ );
29
+ const component = page.locator(componentName);
30
+ expect(await component.screenshot()).toMatchSnapshot();
31
+ });
32
+ });
33
+
34
+ test.describe('logic', () => {
35
+ test('copy to clipboard', async ({ page, browserName }) => {
36
+ test.skip(browserName !== 'chromium', 'test can run only in chromium');
37
+
38
+ await page.goto(getStoryUrl(storyName, { 'copy-to-clipboard': true }), {
39
+ waitUntil: 'networkidle',
40
+ });
41
+
42
+ const component = createRecoveryCodesTestDriver(page.locator(componentName));
43
+
44
+ // clear clipboard
45
+ await page.evaluate("navigator.clipboard.writeText('')");
46
+ expect(await component.screenshot()).toMatchSnapshot('before_clipboard.png');
47
+
48
+ await component.copyToClipboard();
49
+
50
+ const clipboardValue = await page.evaluate('navigator.clipboard.readText()');
51
+
52
+ expect(clipboardValue).toBe(`11111-11111\n11111-21111\n11111-31111\n11111-41111\n11111-51111\n11111-61111\n11111-71111\n11111-81111\n11111-91111`);
53
+ expect(await component.screenshot()).toMatchSnapshot('after_clipboard.png');
54
+ });
55
+ });
56
+
57
+ test.describe('presets', () => {
58
+ loopPresets({
59
+ 'full width LTR': { 'full-width': 'true', direction: 'ltr', 'text-align': 'left' },
60
+ 'full width RTL': { 'full-width': 'true', direction: 'rtl', 'text-align': 'right' },
61
+ }, (preset, name) => {
62
+ test(name, async ({ page }) => {
63
+ await page.goto(getStoryUrl(storyName, preset));
64
+ await page.waitForSelector(componentName);
65
+ const component = page.locator(componentName);
66
+ expect(
67
+ await component.screenshot({ animations: 'disabled', timeout: 3000, caret: 'hide' })
68
+ ).toMatchSnapshot();
69
+ });
70
+ });
71
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@descope-ui/descope-recovery-codes",
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/RecoveryCodesClass.js"
13
+ }
14
+ },
15
+ "devDependencies": {
16
+ "@playwright/test": "1.38.1",
17
+ "e2e-utils": "0.0.1",
18
+ "test-drivers": "0.0.1"
19
+ },
20
+ "dependencies": {
21
+ "@vaadin/icon": "24.3.4",
22
+ "@vaadin/icons": "24.3.4",
23
+ "@descope-ui/common": "0.0.15",
24
+ "@descope-ui/theme-globals": "0.0.15",
25
+ "@descope-ui/descope-text": "0.0.15"
26
+ },
27
+ "publishConfig": {
28
+ "link-workspace-packages": false
29
+ },
30
+ "scripts": {
31
+ "test": "echo 'No tests defined' && exit 0",
32
+ "test:e2e": "echo 'No e2e tests defined' && exit 0"
33
+ }
34
+ }
package/project.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@descope-ui/descope-recovery-codes",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/web-components/components/descope-recovery-codes/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,145 @@
1
+ import {
2
+ createStyleMixin,
3
+ draggableMixin,
4
+ componentNameValidationMixin,
5
+ createDynamicDataMixin,
6
+ } from '@descope-ui/common/components-mixins';
7
+ import { compose } from '@descope-ui/common/utils';
8
+ import {
9
+ forwardAttrs,
10
+ getComponentName,
11
+ injectStyle,
12
+ } from '@descope-ui/common/components-helpers';
13
+ import { createBaseClass } from '@descope-ui/common/base-classes';
14
+ import { sortData } from './helpers';
15
+ import { TextClass } from '@descope-ui/descope-text/class';
16
+
17
+ export const componentName = getComponentName('recovery-codes');
18
+
19
+ const itemRenderer = ({ value }, _, ref) => {
20
+ return `
21
+ <descope-text variant="${ref.variant}" mode="${ref.mode}">
22
+ <span>${value}</span>
23
+ </descope-text>
24
+ `;
25
+ };
26
+
27
+ class RawRecoveryCodes extends createBaseClass({
28
+ componentName,
29
+ baseSelector: ':host > div',
30
+ }) {
31
+ constructor() {
32
+ super();
33
+
34
+ this.attachShadow({ mode: 'open' }).innerHTML = `
35
+ <div class="wrapper">
36
+ <div class="list"></div>
37
+ <descope-text class="icon-wrapper">
38
+ <vaadin-icon class="icon" icon="vaadin:copy-o" title="Copy"></vaadin-icon>
39
+ </descope-text>
40
+ </div>
41
+ `;
42
+
43
+ injectStyle(
44
+ `
45
+ :host {
46
+ display: inline-block;
47
+ position: relative;
48
+ }
49
+ .wrapper {
50
+ display: flex;
51
+ justify-content: space-between;
52
+ }
53
+ .list {
54
+ display: flex;
55
+ flex-grow: 1;
56
+ flex-direction: column;
57
+ }
58
+ descope-text span {
59
+ display: inline-flex;
60
+ align-items: center;
61
+ width: fit-content;
62
+ }
63
+ descope-text span::before {
64
+ content: '\\02022';
65
+ transform: translateY(-0.06em);
66
+ }
67
+ vaadin-icon {
68
+ cursor: pointer;
69
+ }
70
+ .copied {
71
+ cursor: initial;
72
+ }
73
+ .icon-wrapper {
74
+ align-self: flex-start;
75
+ }
76
+ `,
77
+ this,
78
+ );
79
+ }
80
+
81
+ get variant() {
82
+ return this.getAttribute('variant');
83
+ }
84
+
85
+ get mode() {
86
+ return this.getAttribute('mode');
87
+ }
88
+
89
+ init() {
90
+ super.init();
91
+
92
+ this.list = this.shadowRoot.querySelector('.list');
93
+ this.icon = this.shadowRoot.querySelector('.icon')
94
+ this.iconWrapper = this.shadowRoot.querySelector('.icon-wrapper')
95
+
96
+ this.icon.addEventListener('click', this.onCopy.bind(this));
97
+
98
+ forwardAttrs(this, this.iconWrapper, { includeAttrs: ['variant', 'mode'] });
99
+ }
100
+
101
+ onCopy() {
102
+ navigator.clipboard.writeText(
103
+ String(this.data.map((rc) => rc.value).join('\n')),
104
+ );
105
+
106
+ this.icon.setAttribute('icon', 'vaadin:check-circle-o');
107
+ this.icon.setAttribute('title', 'Copied');
108
+ this.icon.classList.add('copied');
109
+
110
+ setTimeout(() => {
111
+ this.icon.setAttribute('icon', 'vaadin:copy-o');
112
+ this.icon.setAttribute('title', 'Copy');
113
+ this.icon.classList.remove('copied');
114
+ }, 5000);
115
+ }
116
+ }
117
+
118
+ export const RecoveryCodesClass = compose(
119
+ createStyleMixin({
120
+ mappings: {
121
+ hostWidth: { selector: () => ':host', property: 'width' },
122
+ hostMinWidth: { selector: () => ':host', property: 'min-width' },
123
+ hostDirection: [
124
+ { selector: () => ':host', property: 'direction' },
125
+ {
126
+ selector: () => TextClass.componentName,
127
+ property: TextClass.cssVarList.hostDirection
128
+ },
129
+ ],
130
+ textAlign: { selector: () => '.list', property: 'align-items' },
131
+ iconColor: { selector: () => 'vaadin-icon', property: 'color' },
132
+ iconSize: { selector: () => 'vaadin-icon', property: 'font-size' },
133
+ iconGap: { selector: () => '.wrapper', property: 'gap' },
134
+ bulletGap: { selector: () => 'descope-text span', property: 'gap' }
135
+ },
136
+ }),
137
+ createDynamicDataMixin({
138
+ itemRenderer,
139
+ rerenderAttrsList: ['variant', 'mode'],
140
+ targetSelector: '.list',
141
+ sortFn: sortData
142
+ }),
143
+ draggableMixin,
144
+ componentNameValidationMixin,
145
+ )(RawRecoveryCodes);
@@ -0,0 +1 @@
1
+ export const sortData = (a, b) => a.value.localeCompare(b.value)
@@ -0,0 +1,9 @@
1
+ import '@descope-ui/descope-text';
2
+ import '@vaadin/icons';
3
+ import '@vaadin/icon';
4
+
5
+ import { componentName, RecoveryCodesClass } from './RecoveryCodesClass';
6
+
7
+ customElements.define(componentName, RecoveryCodesClass);
8
+
9
+ export { RecoveryCodesClass, componentName };
package/src/theme.js ADDED
@@ -0,0 +1,30 @@
1
+ import globals from '@descope-ui/theme-globals';
2
+ import { getThemeRefs, useVar } from '@descope-ui/common/theme-helpers';
3
+ import { TextClass } from '@descope-ui/descope-text/class';
4
+ import { RecoveryCodesClass } from './component/RecoveryCodesClass';
5
+
6
+ const globalRefs = getThemeRefs(globals);
7
+ const vars = RecoveryCodesClass.cssVarList;
8
+ const textVars = TextClass.cssVarList;
9
+
10
+ const recoveryCodes = {
11
+ [vars.hostMinWidth]: '190px',
12
+ [vars.hostDirection]: globalRefs.direction,
13
+ [vars.iconColor]: useVar(textVars.textColor),
14
+ [vars.iconSize]: useVar(textVars.fontSize),
15
+ [vars.iconGap]: '8px',
16
+ [vars.bulletGap]: '0.35em',
17
+
18
+ textAlign: {
19
+ right: { [vars.textAlign]: 'flex-end' },
20
+ left: { [vars.textAlign]: 'flex-start' },
21
+ center: { [vars.textAlign]: 'center' },
22
+ },
23
+
24
+ _fullWidth: {
25
+ [vars.hostWidth]: '100%',
26
+ },
27
+ };
28
+
29
+ export default recoveryCodes;
30
+ export { vars };
@@ -0,0 +1,92 @@
1
+ import { componentName } from '../src/component';
2
+ import {
3
+ textAlignControl,
4
+ modeControl,
5
+ fullWidthControl,
6
+ typographyVariantControl,
7
+ directionControl,
8
+ } from '@descope-ui/common/sb-controls';
9
+
10
+ const serializedData = [
11
+ { value: '11111-11111' },
12
+ { value: '11111-21111' },
13
+ { value: '11111-31111' },
14
+ { value: '11111-41111' },
15
+ { value: '11111-51111' },
16
+ { value: '11111-61111' },
17
+ { value: '11111-71111' },
18
+ { value: '11111-81111' },
19
+ { value: '11111-91111' },
20
+ ];
21
+
22
+ const Template = ({
23
+ itemsSource,
24
+ variant,
25
+ mode,
26
+ 'text-align': textAlign,
27
+ 'full-width': fullWidth,
28
+ direction,
29
+ }) => `
30
+ <descope-recovery-codes
31
+ data='${(itemsSource === 'attr' && serializedData) || ''}'
32
+ mode="${mode || ''}"
33
+ variant="${variant || ''}"
34
+ full-width="${fullWidth || false}"
35
+ text-align="${textAlign}"
36
+ st-host-direction="${direction ?? ''}"
37
+ ></descope-recovery-codes>
38
+ `;
39
+
40
+ export default {
41
+ component: componentName,
42
+ title: 'descope-recovery-codes',
43
+ parameters: {
44
+ panelPosition: 'right',
45
+ controls: { expanded: true },
46
+ },
47
+ decorators: [
48
+ (story, { args }) => {
49
+ setTimeout(() => {
50
+ const ele = document.querySelector('descope-recovery-codes');
51
+ if (!ele) return;
52
+
53
+ if (args.itemsSource === 'prop') {
54
+ ele.data = args.data;
55
+ }
56
+
57
+ if (args.itemsSource === 'attr') {
58
+ ele.setAttribute('data', JSON.stringify(args.data));
59
+ }
60
+ });
61
+
62
+ return story();
63
+ },
64
+ ],
65
+ argTypes: {
66
+ ...modeControl,
67
+ ...textAlignControl,
68
+ ...fullWidthControl,
69
+ ...typographyVariantControl,
70
+ ...directionControl,
71
+ itemsSource: {
72
+ name: 'Where to take the items from',
73
+ control: {
74
+ type: 'select',
75
+ labels: {
76
+ attr: '"data" attribute',
77
+ prop: '"data" property',
78
+ },
79
+ },
80
+ options: ['attr', 'prop'],
81
+ },
82
+ },
83
+ };
84
+
85
+ export const Default = Template.bind({});
86
+
87
+ Default.args = {
88
+ variant: 'body2',
89
+ mode: 'primary',
90
+ data: serializedData,
91
+ itemsSource: 'attr'
92
+ };
@@ -0,0 +1,14 @@
1
+ import { Locator } from '@playwright/test';
2
+ import { createComponentTestDriver } from 'test-drivers';
3
+
4
+ const createRecoveryCodesTestDriver = (locator: Locator) => {
5
+ return {
6
+ ...createComponentTestDriver(locator),
7
+
8
+ async copyToClipboard() {
9
+ return locator.locator('vaadin-icon').click();
10
+ },
11
+ };
12
+ };
13
+
14
+ export default createRecoveryCodesTestDriver;