diff --git a/package-lock.json b/package-lock.json index c85903b6..61177228 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "semantic-release": "^19.0.5" }, "engines": { - "node": "^20.11.1", + "node": "^22.11.0", "npm": "^10.0.0" } }, @@ -31581,10 +31581,10 @@ }, "packages/core-components": { "name": "@otto-de/b2b-core-components", - "version": "1.18.0", + "version": "1.22.0", "license": "Apache-2.0", "dependencies": { - "@otto-de/b2b-tokens": "1.18.0", + "@otto-de/b2b-tokens": "1.22.0", "@stencil-community/eslint-plugin": "^0.5.0", "@stencil/core": "^4.12.4" }, @@ -31832,10 +31832,10 @@ }, "packages/react-components": { "name": "@otto-de/b2b-react-components", - "version": "1.18.0", + "version": "1.22.0", "license": "Apache-2.0", "dependencies": { - "@otto-de/b2b-core-components": "1.18.0" + "@otto-de/b2b-core-components": "1.22.0" }, "devDependencies": { "@types/node": "^18.17.1", @@ -31859,7 +31859,7 @@ }, "packages/tokens": { "name": "@otto-de/b2b-tokens", - "version": "1.18.0", + "version": "1.22.0", "license": "Apache-2.0", "devDependencies": { "lodash": "^4.17.21", @@ -35224,7 +35224,7 @@ "@babel/core": "^7.21.3", "@babel/preset-typescript": "^7.22.15", "@fullhuman/postcss-purgecss": "^5.0.0", - "@otto-de/b2b-tokens": "1.18.0", + "@otto-de/b2b-tokens": "1.22.0", "@stencil-community/eslint-plugin": "^0.5.0", "@stencil-community/postcss": "^2.2.0", "@stencil/core": "^4.12.4", @@ -35404,7 +35404,7 @@ "@otto-de/b2b-react-components": { "version": "file:packages/react-components", "requires": { - "@otto-de/b2b-core-components": "1.18.0", + "@otto-de/b2b-core-components": "1.22.0", "@types/node": "^18.17.1", "@types/react": "^17.0.62", "@types/react-dom": "^17.0.20", diff --git a/packages/core-components/src/components.d.ts b/packages/core-components/src/components.d.ts index ce7c2a24..3af84f7d 100644 --- a/packages/core-components/src/components.d.ts +++ b/packages/core-components/src/components.d.ts @@ -1000,6 +1000,20 @@ export namespace Components { */ "alignment": 'vertical' | 'horizontal'; } + interface B2bShimmer { + /** + * The height of the shimmer effect in px. + */ + "height": number; + /** + * Whether the shimmer effect is shown or not. + */ + "loading": boolean; + /** + * The width of the shimmer effect im px. + */ + "width": number; + } interface B2bSnackbar { /** * Text for the Call-to-Action link. @@ -2087,6 +2101,12 @@ declare global { prototype: HTMLB2bSeparatorElement; new (): HTMLB2bSeparatorElement; }; + interface HTMLB2bShimmerElement extends Components.B2bShimmer, HTMLStencilElement { + } + var HTMLB2bShimmerElement: { + prototype: HTMLB2bShimmerElement; + new (): HTMLB2bShimmerElement; + }; interface HTMLB2bSnackbarElementEventMap { "b2b-close": void; "b2b-action-click": void; @@ -2395,6 +2415,7 @@ declare global { "b2b-scrollable-container": HTMLB2bScrollableContainerElement; "b2b-search": HTMLB2bSearchElement; "b2b-separator": HTMLB2bSeparatorElement; + "b2b-shimmer": HTMLB2bShimmerElement; "b2b-snackbar": HTMLB2bSnackbarElement; "b2b-spinner": HTMLB2bSpinnerElement; "b2b-tab": HTMLB2bTabElement; @@ -3520,6 +3541,20 @@ declare namespace LocalJSX { */ "alignment"?: 'vertical' | 'horizontal'; } + interface B2bShimmer { + /** + * The height of the shimmer effect in px. + */ + "height"?: number; + /** + * Whether the shimmer effect is shown or not. + */ + "loading"?: boolean; + /** + * The width of the shimmer effect im px. + */ + "width"?: number; + } interface B2bSnackbar { /** * Text for the Call-to-Action link. @@ -4005,6 +4040,7 @@ declare namespace LocalJSX { "b2b-scrollable-container": B2bScrollableContainer; "b2b-search": B2bSearch; "b2b-separator": B2bSeparator; + "b2b-shimmer": B2bShimmer; "b2b-snackbar": B2bSnackbar; "b2b-spinner": B2bSpinner; "b2b-tab": B2bTab; @@ -4091,6 +4127,7 @@ declare module "@stencil/core" { "b2b-scrollable-container": LocalJSX.B2bScrollableContainer & JSXBase.HTMLAttributes; "b2b-search": LocalJSX.B2bSearch & JSXBase.HTMLAttributes; "b2b-separator": LocalJSX.B2bSeparator & JSXBase.HTMLAttributes; + "b2b-shimmer": LocalJSX.B2bShimmer & JSXBase.HTMLAttributes; "b2b-snackbar": LocalJSX.B2bSnackbar & JSXBase.HTMLAttributes; /** * Spinner component to display loading indicator. diff --git a/packages/core-components/src/components/shimmer/readme.md b/packages/core-components/src/components/shimmer/readme.md new file mode 100644 index 00000000..8282ae9e --- /dev/null +++ b/packages/core-components/src/components/shimmer/readme.md @@ -0,0 +1,19 @@ +# b2b-shimmer + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| --------- | --------- | ------------------------------------------- | --------- | ----------- | +| `height` | `height` | The height of the shimmer effect in px. | `number` | `undefined` | +| `loading` | `loading` | Whether the shimmer effect is shown or not. | `boolean` | `undefined` | +| `width` | `width` | The width of the shimmer effect im px. | `number` | `undefined` | + + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/packages/core-components/src/components/shimmer/shimmer.docs.mdx b/packages/core-components/src/components/shimmer/shimmer.docs.mdx new file mode 100644 index 00000000..612becbb --- /dev/null +++ b/packages/core-components/src/components/shimmer/shimmer.docs.mdx @@ -0,0 +1,15 @@ +import { ArgsTable, PRIMARY_STORY, Primary, Meta } from '@storybook/blocks'; + +import * as ShimmerStories from './shimmer.stories'; + + + +# Shimmer + +The shimmer effect is an animated placeholder shown while the actual content is being loaded. + +## Attributes + + +Changes made to the attributes in the above table will reflect in the example below: + diff --git a/packages/core-components/src/components/shimmer/shimmer.e2e.tsx b/packages/core-components/src/components/shimmer/shimmer.e2e.tsx new file mode 100644 index 00000000..b702c608 --- /dev/null +++ b/packages/core-components/src/components/shimmer/shimmer.e2e.tsx @@ -0,0 +1,25 @@ +import { newE2EPage } from '@stencil/core/testing'; + +describe('B2B-Shimmer', () => { + let page; + beforeEach(async () => { + page = await newE2EPage(); + await page.setContent( + '', + ); + }); + + it('should render shimmer component', async () => { + const element = await page.find('b2b-shimmer'); + expect(element).toBeDefined(); + }); + + it('should show slot content if loading is false', async () => { + const page = await newE2EPage(); + await page.setContent('

dummy content

'); + + const element = await page.find('h1'); + + expect(element).toEqualText('dummy content'); + }); +}); diff --git a/packages/core-components/src/components/shimmer/shimmer.scss b/packages/core-components/src/components/shimmer/shimmer.scss new file mode 100644 index 00000000..5c7399a0 --- /dev/null +++ b/packages/core-components/src/components/shimmer/shimmer.scss @@ -0,0 +1,29 @@ +@use '../../global/b2b-styles'; + +.b2b-shimmer { + position: relative; + overflow: hidden; + border-radius: 3px; + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: var(--b2b-color-grey-150); + animation: b2b-shimmer 2s infinite; + } +} + +@keyframes b2b-shimmer { + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } +} diff --git a/packages/core-components/src/components/shimmer/shimmer.spec.tsx b/packages/core-components/src/components/shimmer/shimmer.spec.tsx new file mode 100644 index 00000000..ab2a7640 --- /dev/null +++ b/packages/core-components/src/components/shimmer/shimmer.spec.tsx @@ -0,0 +1,46 @@ +import { newSpecPage } from '@stencil/core/testing'; +import { ShimmerComponent } from './shimmer'; +import { h } from '@stencil/core'; + +describe('B2B-Shimmer', () => { + async function renderPage(template) { + return newSpecPage({ + components: [ShimmerComponent], + template: () => template, + }); + } + + it('should render shimmer effect with width 100 and height 100 attributes', async () => { + const dummyContent = 'Dummy content'; + const page = await renderPage( + + {dummyContent} + , + ); + expect(page.root).toEqualHtml(` + + +
+
+ ${dummyContent} +
+ `); + }); + + it('should show slot content and hide shimmer effect', async () => { + const dummyContent = 'Dummy content'; + const page = await renderPage( + + {dummyContent} + , + ); + expect(page.root).toEqualHtml(` + + + + + ${dummyContent} + + `); + }); +}); diff --git a/packages/core-components/src/components/shimmer/shimmer.stories.tsx b/packages/core-components/src/components/shimmer/shimmer.stories.tsx new file mode 100644 index 00000000..2d7e155f --- /dev/null +++ b/packages/core-components/src/components/shimmer/shimmer.stories.tsx @@ -0,0 +1,33 @@ +import { Meta, StoryObj } from '@storybook/web-components'; +import { html } from 'lit-html'; +import { getArgTypes } from '../../docs/config/utils'; + +type Story = StoryObj; + +const meta: Meta = { + title: 'Components/Status & Feedback/Shimmer', + component: 'b2b-shimmer', + args: { + loading: true, + width: 400, + height: 25, + }, + argTypes: { + ...getArgTypes('b2b-shimmer'), + }, + render: ({ ...args }) => html` + This is the mean content which takes a while to load. + `, +}; + +export default meta; + +export const story010Default: Story = { + name: 'Default', + args: { + ...meta.args, + }, +}; diff --git a/packages/core-components/src/components/shimmer/shimmer.tsx b/packages/core-components/src/components/shimmer/shimmer.tsx new file mode 100644 index 00000000..48ac8063 --- /dev/null +++ b/packages/core-components/src/components/shimmer/shimmer.tsx @@ -0,0 +1,34 @@ +import { Component, Prop, h, Host } from '@stencil/core'; + +@Component({ + tag: 'b2b-shimmer', + styleUrl: 'shimmer.scss', + shadow: true, +}) +export class ShimmerComponent { + /** Whether the shimmer effect is shown or not. */ + @Prop() loading: boolean; + + /** The width of the shimmer effect im px. */ + @Prop() width: number; + + /** The height of the shimmer effect in px. */ + @Prop() height: number; + + render() { + const shimmerStyle = { + width: `${this.width}px`, + height: `${this.height}px`, + }; + + return ( + + {this.loading ? ( +
+ ) : ( + + )} + + ); + } +} diff --git a/packages/core-components/src/docs/config/components-args.json b/packages/core-components/src/docs/config/components-args.json index 801608c6..44100d52 100644 --- a/packages/core-components/src/docs/config/components-args.json +++ b/packages/core-components/src/docs/config/components-args.json @@ -3501,6 +3501,38 @@ "description": "The alignment of the separator. Per default it is horizontal." } }, + "b2b-shimmer": { + "height": { + "table": { + "type": { + "summary": "number" + }, + "defaultValue": {} + }, + "description": "The height of the shimmer effect in px." + }, + "loading": { + "control": { + "type": "boolean" + }, + "table": { + "type": { + "summary": "boolean" + }, + "defaultValue": {} + }, + "description": "Whether the shimmer effect is shown or not." + }, + "width": { + "table": { + "type": { + "summary": "number" + }, + "defaultValue": {} + }, + "description": "The width of the shimmer effect im px." + } + }, "b2b-snackbar": { "actionLabel": { "table": { diff --git a/packages/core-components/src/html/status.html b/packages/core-components/src/html/status.html index fb7f9a2d..d50ac41f 100644 --- a/packages/core-components/src/html/status.html +++ b/packages/core-components/src/html/status.html @@ -77,6 +77,15 @@ Loading
+
+ Shimmer Effect +
+ + This is the mean content which takes a while to load. + +
+
+
Modal
diff --git a/packages/react-components/src/components/stencil-generated/index.ts b/packages/react-components/src/components/stencil-generated/index.ts index d4444f24..13c5d84a 100644 --- a/packages/react-components/src/components/stencil-generated/index.ts +++ b/packages/react-components/src/components/stencil-generated/index.ts @@ -53,6 +53,7 @@ export const B2bRoundedIcon = /*@__PURE__*/createReactComponent('b2b-scrollable-container'); export const B2bSearch = /*@__PURE__*/createReactComponent('b2b-search'); export const B2bSeparator = /*@__PURE__*/createReactComponent('b2b-separator'); +export const B2bShimmer = /*@__PURE__*/createReactComponent('b2b-shimmer'); export const B2bSnackbar = /*@__PURE__*/createReactComponent('b2b-snackbar'); export const B2bSpinner = /*@__PURE__*/createReactComponent('b2b-spinner'); export const B2bTab = /*@__PURE__*/createReactComponent('b2b-tab');