@descope-ui/descope-list 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-07-21)
6
+
7
+ ### Dependency Updates
8
+
9
+ * `e2e-utils` updated to version `0.0.1`
10
+ * `@descope-ui/descope-avatar` updated to version `0.0.19`
11
+ * `@descope-ui/common` updated to version `0.0.18`
12
+ * `@descope-ui/theme-globals` updated to version `0.0.19`
13
+ * `@descope-ui/descope-list-item` updated to version `0.0.1`
14
+ # Changelog
@@ -0,0 +1,26 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import { getStoryUrl, loopConfig } from 'e2e-utils';
3
+
4
+ const componentAttributes = {
5
+ direction: ['ltr', 'rtl'],
6
+ numberOfItems: ['0'],
7
+ variant: ['list', 'tiles'],
8
+ };
9
+
10
+ const storyName = 'descope-list';
11
+ const componentName = 'descope-list';
12
+
13
+ test.describe('theme', () => {
14
+ loopConfig(componentAttributes, (attr, value) => {
15
+ test.describe(`${attr}: ${value}`, () => {
16
+ test.beforeEach(async ({ page }) => {
17
+ await page.goto(getStoryUrl(storyName, { [attr]: value }), { waitUntil: 'networkidle' });
18
+ });
19
+ test('style', async ({ page }) => {
20
+ const componentParent = page.locator(componentName);
21
+
22
+ expect(await componentParent.screenshot()).toMatchSnapshot();
23
+ });
24
+ });
25
+ });
26
+ });
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@descope-ui/descope-list",
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/ListClass.js"
13
+ }
14
+ },
15
+ "devDependencies": {
16
+ "@playwright/test": "1.38.1",
17
+ "e2e-utils": "0.0.1",
18
+ "@descope-ui/descope-avatar": "0.0.19"
19
+ },
20
+ "dependencies": {
21
+ "@descope-ui/common": "0.0.18",
22
+ "@descope-ui/theme-globals": "0.0.19",
23
+ "@descope-ui/descope-list-item": "0.0.1"
24
+ },
25
+ "publishConfig": {
26
+ "link-workspace-packages": false
27
+ },
28
+ "scripts": {
29
+ "test": "echo 'No tests defined' && exit 0",
30
+ "test:e2e": "echo 'No e2e tests defined' && exit 0"
31
+ }
32
+ }
package/project.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@descope-ui/descope-list",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/web-components/components/descope-list/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,163 @@
1
+ import {
2
+ createStyleMixin,
3
+ draggableMixin,
4
+ componentNameValidationMixin,
5
+ } from '@descope-ui/common/components-mixins';
6
+ import { compose } from '@descope-ui/common/utils';
7
+ import {
8
+ getComponentName,
9
+ observeChildren,
10
+ } from '@descope-ui/common/components-helpers';
11
+ import { injectStyle } from '@descope-ui/common/components-helpers';
12
+ import { ListItemClass } from '@descope-ui/descope-list-item/class';
13
+ import { createBaseClass } from '@descope-ui/common/base-classes';
14
+
15
+ export const componentName = getComponentName('list');
16
+
17
+ class RawList extends createBaseClass({ componentName, baseSelector: '.wrapper' }) {
18
+ static get observedAttributes() {
19
+ return ['variant', 'readonly'];
20
+ }
21
+
22
+ constructor() {
23
+ super();
24
+
25
+ this.attachShadow({ mode: 'open' }).innerHTML = `
26
+ <div class="wrapper">
27
+ <slot></slot>
28
+ <slot name="empty-state">
29
+ No item...
30
+ </slot>
31
+ </div>
32
+ `;
33
+
34
+ injectStyle(
35
+ `
36
+ .wrapper {
37
+ overflow: auto;
38
+ display: grid;
39
+ max-height: 100%;
40
+ width: 100%;
41
+ }
42
+
43
+ :host {
44
+ display: inline-flex;
45
+ width: 100%;
46
+ }
47
+ slot[name="empty-state"] {
48
+ justify-content: center;
49
+ align-items: center;
50
+ display: flex;
51
+ flex-grow: 1;
52
+ }
53
+
54
+ :host slot[name="empty-state"] {
55
+ display: none;
56
+ }
57
+ :host([empty]) slot[name="empty-state"] {
58
+ display: flex;
59
+ }
60
+ ::slotted(:not([slot])) {
61
+ width: 100%;
62
+ }
63
+ `,
64
+ this
65
+ );
66
+ }
67
+
68
+ get items() {
69
+ return this.shadowRoot.querySelector('slot').assignedElements();
70
+ }
71
+
72
+ #handleEmptyState() {
73
+ if (this.items.length === 0) {
74
+ this.setAttribute('empty', 'true');
75
+ } else {
76
+ this.removeAttribute('empty');
77
+ }
78
+ }
79
+
80
+ get variant() {
81
+ return this.getAttribute('variant') || 'list';
82
+ }
83
+
84
+ #handleItemsVariant() {
85
+ this.items.forEach((item) => {
86
+ let listItem = item;
87
+ if (listItem.localName !== ListItemClass.componentName) {
88
+ listItem = item.querySelector(ListItemClass.componentName);
89
+ }
90
+
91
+ const listItemVariant = this.variant === 'tiles' ? 'tile' : 'row';
92
+ listItem.setAttribute('variant', listItemVariant);
93
+ });
94
+ }
95
+
96
+ init() {
97
+ super.init?.();
98
+
99
+ // we want new items to get the size
100
+ observeChildren(this, () => {
101
+ this.#handleEmptyState();
102
+ this.#handleItemsVariant();
103
+ this.#handleReadOnly();
104
+ });
105
+ }
106
+
107
+ get isReadOnly() {
108
+ return this.getAttribute('readonly') === 'true';
109
+ }
110
+
111
+ #handleReadOnly() {
112
+ this.items.forEach((item) => {
113
+ if (this.isReadOnly) item.setAttribute('inert', '');
114
+ else item.removeAttribute('inert');
115
+ });
116
+ }
117
+
118
+ attributeChangedCallback(name, oldValue, newValue) {
119
+ super.attributeChangedCallback?.(name, oldValue, newValue);
120
+
121
+ if (newValue === oldValue) return;
122
+
123
+ if (name === 'variant') {
124
+ this.#handleItemsVariant();
125
+ } else if (name === 'readonly') {
126
+ this.#handleReadOnly();
127
+ }
128
+ }
129
+ }
130
+
131
+ export const ListClass = compose(
132
+ createStyleMixin({
133
+ mappings: {
134
+ hostWidth: { selector: () => ':host', property: 'width' },
135
+ maxHeight: { selector: () => ':host' },
136
+ minHeight: {},
137
+ verticalPadding: [{ property: 'padding-top' }, { property: 'padding-bottom' }],
138
+ horizontalPadding: [{ property: 'padding-left' }, { property: 'padding-right' }],
139
+ hostDirection: { selector: () => ':host', property: 'direction' },
140
+ fontFamily: {},
141
+ gap: {},
142
+
143
+ backgroundColor: {},
144
+ borderRadius: {},
145
+ borderColor: {},
146
+ borderStyle: {},
147
+ borderWidth: {},
148
+
149
+ boxShadow: {},
150
+ gridTemplateColumns: {},
151
+ maxItemsWidth: { selector: () => '::slotted(:not([slot]))', property: 'max-width' },
152
+ minItemsWidth: { selector: () => '::slotted(:not([slot]))', property: 'min-width' },
153
+ itemsHorizontalAlign: { selector: () => '::slotted(*)', property: 'justify-self' },
154
+ emptyStateTextColor: { selector: () => 'slot[name="empty-state"]', property: 'color' },
155
+ emptyStateTextFontFamily: {
156
+ selector: () => 'slot[name="empty-state"]',
157
+ property: 'font-family',
158
+ },
159
+ },
160
+ }),
161
+ draggableMixin,
162
+ componentNameValidationMixin
163
+ )(RawList);
@@ -0,0 +1,7 @@
1
+ import '@descope-ui/descope-list-item';
2
+
3
+ import { componentName, ListClass } from './ListClass';
4
+
5
+ customElements.define(componentName, ListClass);
6
+
7
+ export { ListClass, componentName };
package/src/theme.js ADDED
@@ -0,0 +1,60 @@
1
+ import globals from '@descope-ui/theme-globals';
2
+ import {
3
+ getThemeRefs,
4
+ createHelperVars,
5
+ useVar,
6
+ } from '@descope-ui/common/theme-helpers';
7
+ import { ListClass, componentName } from './component/ListClass';
8
+
9
+ const globalRefs = getThemeRefs(globals);
10
+
11
+ const compVars = ListClass.cssVarList;
12
+
13
+ const [helperTheme, helperRefs, helperVars] = createHelperVars(
14
+ { shadowColor: '#00000020' },
15
+ componentName
16
+ );
17
+
18
+ const { shadowColor } = helperRefs;
19
+
20
+ const list = {
21
+ ...helperTheme,
22
+
23
+ [compVars.hostWidth]: '100%',
24
+ [compVars.backgroundColor]: globalRefs.colors.surface.main,
25
+ [compVars.fontFamily]: globalRefs.fonts.font1.family,
26
+ [compVars.borderColor]: globalRefs.colors.surface.light,
27
+ [compVars.borderStyle]: 'solid',
28
+ [compVars.borderWidth]: globalRefs.border.xs,
29
+ [compVars.borderRadius]: globalRefs.radius.sm,
30
+ [compVars.gap]: globalRefs.spacing.md,
31
+ [compVars.verticalPadding]: globalRefs.spacing.lg,
32
+ [compVars.horizontalPadding]: globalRefs.spacing.lg,
33
+ [compVars.boxShadow]: `${globalRefs.shadow.wide.sm} ${shadowColor}, ${globalRefs.shadow.narrow.sm} ${shadowColor}`,
34
+ [compVars.maxHeight]: '100%',
35
+ [compVars.hostDirection]: globalRefs.direction,
36
+ [compVars.minItemsWidth]: '150px',
37
+
38
+ _empty: {
39
+ [compVars.minHeight]: '150px',
40
+ [compVars.emptyStateTextColor]: globalRefs.colors.surface.dark,
41
+ [compVars.emptyStateTextFontFamily]: globalRefs.fonts.font1.family,
42
+ },
43
+
44
+ variant: {
45
+ tiles: {
46
+ [compVars.gridTemplateColumns]: `repeat(auto-fit, minmax(min(${useVar(
47
+ compVars.minItemsWidth
48
+ )}, 100%), 1fr))`,
49
+ [compVars.maxItemsWidth]: '200px',
50
+ [compVars.itemsHorizontalAlign]: 'center',
51
+ },
52
+ },
53
+ };
54
+
55
+ export default list;
56
+
57
+ export const vars = {
58
+ ...compVars,
59
+ ...helperVars,
60
+ };
Binary file
@@ -0,0 +1,55 @@
1
+ import { componentName } from '../src/component';
2
+ import { directionControl } from '@descope-ui/common/sb-controls';
3
+ import imgUrl from './avatar.jpeg?no-inline';
4
+ import '@descope-ui/descope-avatar';
5
+
6
+ const Template = ({
7
+ direction,
8
+ numberOfItems,
9
+ itemSampleText,
10
+ emptyState,
11
+ variant,
12
+ }) => `
13
+ <descope-list
14
+ st-host-direction="${direction || ''}"
15
+ variant="${variant}"
16
+ >
17
+ ${Array.from(
18
+ { length: numberOfItems || 0 },
19
+ (_, i) => `
20
+ <descope-list-item>
21
+ <descope-avatar
22
+ size=sm
23
+ display-name="John Doe"
24
+ img="${i % 2 > 0 ? imgUrl : ''}"
25
+ alt="avatar"
26
+ ></descope-avatar>
27
+ <descope-text variant="body1" mode="primary">${itemSampleText} ${i + 1}</descope-text>
28
+ </descope-list-item>
29
+ `,
30
+ ).join('')}
31
+ <div slot="empty-state">${emptyState}</div>
32
+ </descope-list>
33
+ `;
34
+
35
+ export default {
36
+ component: componentName,
37
+ title: 'descope-list',
38
+ argTypes: {
39
+ ...directionControl,
40
+ variant: {
41
+ name: 'Variant',
42
+ options: ['list', 'tiles'],
43
+ control: { type: 'radio' },
44
+ },
45
+ },
46
+ };
47
+
48
+ export const Default = Template.bind({});
49
+
50
+ Default.args = {
51
+ numberOfItems: 10,
52
+ itemSampleText: 'Item',
53
+ emptyState: 'No items in the list...',
54
+ variant: 'list',
55
+ };