@descope-ui/descope-timer-button 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,16 @@
1
+ # Changelog
2
+
3
+ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
+
5
+ ## 0.0.1 (2025-03-11)
6
+
7
+ ### Dependency Updates
8
+
9
+ * `e2e-utils` updated to version `0.0.1`
10
+ * `test-drivers` updated to version `0.0.1`
11
+ * `test-assets` updated to version `0.0.1`
12
+ * `@descope-ui/common` updated to version `0.0.9`
13
+ * `@descope-ui/theme-globals` updated to version `0.0.9`
14
+ * `@descope-ui/descope-timer` updated to version `0.0.1`
15
+ * `@descope-ui/descope-button` updated to version `0.0.4`
16
+ # Changelog
@@ -0,0 +1,148 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { getStoryUrl, loopConfig, loopPresets } from 'e2e-utils';
3
+ import { createTimerButtonTestDriver } from 'test-drivers';
4
+
5
+ const componentAttributes = {
6
+ size: ['xs', 'sm', 'md', 'lg'],
7
+ horizontal: ['true', 'false'],
8
+ 'hide-icon': ['true', 'false'],
9
+ 'full-width': ['true', 'false'],
10
+ 'button-variant': ['contained', 'outline', 'link'],
11
+ 'button-mode': ['primary', 'secondary'],
12
+ icon: ['true', 'false'],
13
+ };
14
+
15
+ const storyName = 'descope-timer-button';
16
+ const componentName = 'descope-timer-button';
17
+
18
+ const presets = {
19
+ 'full width horizontal': { 'full-width': 'true', horizontal: 'true' },
20
+ 'full width vertical': { 'full-width': 'true' },
21
+ };
22
+
23
+ test.describe('theme', () => {
24
+ loopConfig(componentAttributes, (attr, value) => {
25
+ test(`${attr}: ${value}`, async ({ page }) => {
26
+ await page.goto(getStoryUrl(storyName, { [attr]: value }), {
27
+ waitUntil: 'load',
28
+ });
29
+ const component = createTimerButtonTestDriver(
30
+ page.locator(componentName),
31
+ );
32
+ await component.timer.pause();
33
+ expect(await component.screenshot()).toMatchSnapshot();
34
+ });
35
+ });
36
+
37
+ ['left', 'center', 'right'].forEach((alignment) => {
38
+ test(`text-align: ${alignment}`, async ({ page }) => {
39
+ await page.goto(
40
+ getStoryUrl(storyName, {
41
+ 'text-align': alignment,
42
+ 'full-width': 'true',
43
+ }),
44
+ { waitUntil: 'load' },
45
+ );
46
+ const component = createTimerButtonTestDriver(
47
+ page.locator(componentName),
48
+ );
49
+ await component.timer.pause();
50
+ expect(await component.screenshot()).toMatchSnapshot();
51
+ });
52
+ });
53
+ });
54
+
55
+ test.describe('logic', () => {
56
+ test(`button restarts timer`, async ({ page }) => {
57
+ await page.goto(
58
+ getStoryUrl(storyName, {
59
+ seconds: '2',
60
+ }),
61
+ {
62
+ waitUntil: 'load',
63
+ },
64
+ );
65
+ const component = createTimerButtonTestDriver(page.locator(componentName));
66
+
67
+ await component.timer.stop();
68
+ expect(await component.screenshot()).toMatchSnapshot();
69
+
70
+ await component.timer.reset();
71
+ await page.waitForTimeout(3000);
72
+
73
+ expect(await component.screenshot()).toMatchSnapshot();
74
+ });
75
+
76
+ test(`disable button if timer is running`, async ({ page }) => {
77
+ await page.goto(
78
+ getStoryUrl(storyName, {
79
+ seconds: '3',
80
+ }),
81
+ {
82
+ waitUntil: 'load',
83
+ },
84
+ );
85
+ const component = createTimerButtonTestDriver(page.locator(componentName));
86
+
87
+ await component.timer.pause();
88
+ expect(await component.screenshot()).toMatchSnapshot();
89
+
90
+ await component.timer.resume();
91
+ await page.waitForTimeout(1000);
92
+
93
+ await component.timer.pause();
94
+ expect(await component.screenshot()).toMatchSnapshot();
95
+ });
96
+
97
+ test(`enable button if timer finished`, async ({ page }) => {
98
+ await page.goto(getStoryUrl(storyName, { seconds: '2' }), {
99
+ waitUntil: 'load',
100
+ });
101
+
102
+ const component = createTimerButtonTestDriver(page.locator(componentName));
103
+
104
+ await component.timer.stop();
105
+ await component.timer.reset();
106
+ await component.timer.pause();
107
+ expect(await component.screenshot({ delay: 3000 })).toMatchSnapshot();
108
+
109
+ await component.timer.resume();
110
+ await page.waitForTimeout(4000);
111
+ expect(await component.screenshot()).toMatchSnapshot();
112
+ });
113
+
114
+ ['', '-1', '0', '1', '10', '100', '10000', '100000', '100000000'].forEach(
115
+ (seconds) => {
116
+ test(`format time: ${seconds || 'empty'} seconds`, async ({ page }) => {
117
+ await page.goto(getStoryUrl(storyName, { seconds }), {
118
+ waitUntil: 'load',
119
+ });
120
+ const component = createTimerButtonTestDriver(
121
+ page.locator(componentName),
122
+ );
123
+ await component.timer.pause();
124
+ expect(await component.screenshot()).toMatchSnapshot();
125
+ });
126
+ },
127
+ );
128
+ });
129
+
130
+ test.describe('presets', () => {
131
+ ['ltr', 'rtl'].forEach((direction) => {
132
+ loopPresets(presets, (preset, name) => {
133
+ test(`${name} - ${direction}`, async ({ page }) => {
134
+ await page.goto(getStoryUrl(storyName, preset));
135
+
136
+ const component = createTimerButtonTestDriver(
137
+ page.locator(componentName),
138
+ );
139
+
140
+ await component.timer.pause();
141
+
142
+ expect(
143
+ await component.screenshot({ animations: 'disabled' }),
144
+ ).toMatchSnapshot();
145
+ });
146
+ });
147
+ });
148
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@descope-ui/descope-timer-button",
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/TimerButtonClass.js"
13
+ }
14
+ },
15
+ "devDependencies": {
16
+ "@playwright/test": "1.38.1",
17
+ "e2e-utils": "0.0.1",
18
+ "test-drivers": "0.0.1",
19
+ "test-assets": "0.0.1"
20
+ },
21
+ "dependencies": {
22
+ "@descope-ui/common": "0.0.9",
23
+ "@descope-ui/theme-globals": "0.0.9",
24
+ "@descope-ui/descope-timer": "0.0.1",
25
+ "@descope-ui/descope-button": "0.0.4"
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-timer-button",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/web-components/components/descope-timer-button/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,138 @@
1
+ import {
2
+ createStyleMixin,
3
+ draggableMixin,
4
+ componentNameValidationMixin,
5
+ } from '@descope-ui/common/components-mixins';
6
+ import { createBaseClass } from '@descope-ui/common/base-classes';
7
+ import { compose } from '@descope-ui/common/utils';
8
+ import {
9
+ forwardAttrs,
10
+ getComponentName,
11
+ injectStyle,
12
+ } from '@descope-ui/common/components-helpers';
13
+
14
+ export const componentName = getComponentName('timer-button');
15
+
16
+ const buttonAttrs = [
17
+ 'button-variant',
18
+ 'button-mode',
19
+ 'size',
20
+ 'text-align',
21
+ 'full-width',
22
+ ];
23
+
24
+ const mapButtonAttrs = {
25
+ 'button-variant': 'variant',
26
+ 'button-mode': 'mode',
27
+ };
28
+
29
+ const timerAttrs = [
30
+ 'timer-seconds',
31
+ 'timer-hide-icon',
32
+ 'size',
33
+ 'text-align',
34
+ 'full-width',
35
+ ];
36
+
37
+ const mapTimerAttrs = {
38
+ 'timer-seconds': 'seconds',
39
+ 'timer-hide-icon': 'hide-icon',
40
+ };
41
+
42
+ const BaseClass = createBaseClass({
43
+ componentName,
44
+ baseSelector: ':host > div',
45
+ });
46
+
47
+ class RawTimerButton extends BaseClass {
48
+ constructor() {
49
+ super();
50
+
51
+ this.attachShadow({ mode: 'open' }).innerHTML = `
52
+ <div class="wrapper">
53
+ <descope-timer class="timer"></descope-timer>
54
+ <descope-button class="button">
55
+ <slot></slot>
56
+ </descope-button>
57
+ </div>
58
+ `;
59
+
60
+ injectStyle(
61
+ `
62
+ :host {
63
+ display: inline-flex;
64
+ }
65
+ .wrapper {
66
+ display: flex;
67
+ flex-direction: column;
68
+ gap: 0.5em;
69
+ align-items: center;
70
+ width: 100%;
71
+ }
72
+ .timer {
73
+ flex: 1;
74
+ }
75
+ `,
76
+ this,
77
+ );
78
+
79
+ this.timer = this.shadowRoot.querySelector('.timer');
80
+ this.button = this.shadowRoot.querySelector('.button');
81
+
82
+ this.timer.addEventListener('timer-started', () => this.onTimerStarted());
83
+ this.timer.addEventListener('timer-ended', () => this.onTimerEnded());
84
+
85
+ this.button.addEventListener('click', this.onClick.bind(this));
86
+ }
87
+
88
+ init() {
89
+ super.init?.();
90
+
91
+ forwardAttrs(this, this.button, {
92
+ includeAttrs: buttonAttrs,
93
+ mapAttrs: mapButtonAttrs,
94
+ });
95
+
96
+ forwardAttrs(this, this.timer, {
97
+ includeAttrs: timerAttrs,
98
+ mapAttrs: mapTimerAttrs,
99
+ });
100
+ }
101
+
102
+ onTimerStarted() {
103
+ this.toggleButtonDisable(true);
104
+ }
105
+
106
+ onTimerEnded() {
107
+ this.toggleButtonDisable(false);
108
+ }
109
+
110
+ onClick() {
111
+ this.timer.reset();
112
+ }
113
+
114
+ toggleButtonDisable(isDisabled) {
115
+ isDisabled
116
+ ? this.button.setAttribute('disabled', 'true')
117
+ : this.button.removeAttribute('disabled');
118
+ }
119
+ }
120
+
121
+ const { host } = {
122
+ host: { selector: () => ':host' },
123
+ icon: { selector: '.icon' },
124
+ timer: { selector: '.timer' },
125
+ };
126
+
127
+ export const TimerButtonClass = compose(
128
+ createStyleMixin({
129
+ mappings: {
130
+ gap: {},
131
+ flexDirection: {},
132
+ hostWidth: { ...host, property: 'width' },
133
+ hostDirection: { ...host, property: 'direction' },
134
+ },
135
+ }),
136
+ draggableMixin,
137
+ componentNameValidationMixin,
138
+ )(RawTimerButton);
@@ -0,0 +1,7 @@
1
+ import { componentName, TimerButtonClass } from './TimerButtonClass';
2
+ import '@descope-ui/descope-button';
3
+ import '@descope-ui/descope-timer';
4
+
5
+ customElements.define(componentName, TimerButtonClass);
6
+
7
+ export { TimerButtonClass, componentName };
package/src/theme.js ADDED
@@ -0,0 +1,23 @@
1
+ import globals from '@descope-ui/theme-globals';
2
+ import { getThemeRefs } from '@descope-ui/common/theme-helpers';
3
+ import { TimerButtonClass } from './component/TimerButtonClass';
4
+
5
+ const globalRefs = getThemeRefs(globals);
6
+ const vars = TimerButtonClass.cssVarList;
7
+
8
+ const timerButton = {
9
+ [vars.gap]: globalRefs.spacing.sm,
10
+ [vars.flexDirection]: 'column',
11
+
12
+ _horizontal: {
13
+ [vars.flexDirection]: 'row',
14
+ },
15
+
16
+ _fullWidth: {
17
+ [vars.hostWidth]: '100%',
18
+ },
19
+ };
20
+
21
+ export default timerButton;
22
+
23
+ export { vars };
@@ -0,0 +1,89 @@
1
+ import { componentName } from '../src/component';
2
+ import {
3
+ buttonVariantControl,
4
+ directionControl,
5
+ fullWidthControl,
6
+ modeControl,
7
+ sizeControl,
8
+ textAlignControl,
9
+ } from '@descope-ui/common/sb-controls';
10
+ import { base64svg } from 'test-assets';
11
+
12
+ const Template = ({
13
+ 'timer-seconds': timerSeconds,
14
+ 'timer-hide-icon': timerHideIcon,
15
+ size,
16
+ direction,
17
+ horizontal,
18
+ 'full-width': fullWidth,
19
+ 'text-align': textAlign,
20
+ 'button-variant': buttonVariant,
21
+ 'button-mode': buttonMode,
22
+ label,
23
+ icon,
24
+ }) => `
25
+ <descope-timer-button
26
+ timer-seconds="${timerSeconds}"
27
+ timer-hide-icon="${timerHideIcon || false}"
28
+ size="${size}"
29
+ horizontal="${horizontal || false}"
30
+ full-width="${fullWidth || false}"
31
+ text-align="${textAlign}"
32
+ button-variant="${buttonVariant}"
33
+ button-mode="${buttonMode}"
34
+ st-host-direction="${direction ?? ''}"
35
+ >
36
+ ${icon ? `<descope-icon src=${base64svg}></descope-icon>` : ''}
37
+ ${label}
38
+ </descope-timer-button>
39
+ `;
40
+
41
+ export default {
42
+ component: componentName,
43
+ title: 'descope-timer-button',
44
+ argTypes: {
45
+ ...sizeControl,
46
+ ...directionControl,
47
+ ...fullWidthControl,
48
+ ...textAlignControl,
49
+ 'button-variant': buttonVariantControl.variant,
50
+ 'button-mode': modeControl.mode,
51
+ horizontal: {
52
+ name: 'Horizontal',
53
+ control: {
54
+ type: 'boolean',
55
+ },
56
+ },
57
+ 'timer-seconds': {
58
+ name: 'Seconds',
59
+ control: {
60
+ type: 'number',
61
+ },
62
+ },
63
+ 'timer-hide-icon': {
64
+ name: 'Hide Icon',
65
+ control: {
66
+ type: 'boolean',
67
+ },
68
+ },
69
+ icon: {
70
+ control: { type: 'boolean' },
71
+ },
72
+ },
73
+ };
74
+
75
+ export const Default = Template.bind({});
76
+
77
+ Default.args = {
78
+ size: 'sm',
79
+ direction: '',
80
+ 'timer-seconds': 5,
81
+ 'timer-hide-icon': false,
82
+ horizontal: false,
83
+ 'text-align': 'center',
84
+ 'button-variant': 'contained',
85
+ 'button-mode': 'primary',
86
+ 'full-width': false,
87
+ icon: false,
88
+ label: 'Resend',
89
+ };