diff --git a/packages/web/src/scss/components/Skeleton/README.md b/packages/web/src/scss/components/Skeleton/README.md new file mode 100644 index 0000000000..efe9d910f4 --- /dev/null +++ b/packages/web/src/scss/components/Skeleton/README.md @@ -0,0 +1,70 @@ +# Skeleton + +On the parent element, you must use `aria-busy` and `aria-live` attributes to indicate that the content inside is loading. +The `aria-busy` is set to `true` when the content is loading, and `aria-live` is set to `polite` to announce the loading +state to screen readers. + +## Text + +The `Skeleton--text` class is used to create a text skeleton. + +- Number of lines is defined by the number of `Skeleton__item` elements +- Minimum number of lines is 1 + +```html +
+ + +
+``` + +## Heading + +The `Skeleton--heading` class is used to create a heading skeleton. + +```html +
+ + +
+``` + +### Text, Heading Sizes + +The Skeleton component supports the following sizes for text and heading skeletons: + +- `Skeleton--xsmall` +- `Skeleton--small` +- `Skeleton--medium` (default) +- `Skeleton--large` +- `Skeleton--xlarge` + +```html +
+``` + +## Shapes + +Use CSS custom properties to define the width, height, and radius of the shape. + +- The default radius is `--spirit-radius-300` + +- `--spirit-skeleton-shape-width: number{px};` +- `--spirit-skeleton-shape-height: number{px};` +- `--spirit-skeleton-shape-radius: var(--spirit-radius-200);` +- `--spirit-skeleton-shape-radius-tablet: var(--spirit-radius-300);` +- `--spirit-skeleton-shape-radius-desktop: var(--spirit-radius-400);` + +```html +
+``` + +```html +
+``` diff --git a/packages/web/src/scss/components/Skeleton/_Skeleton.scss b/packages/web/src/scss/components/Skeleton/_Skeleton.scss new file mode 100644 index 0000000000..3f9909f589 --- /dev/null +++ b/packages/web/src/scss/components/Skeleton/_Skeleton.scss @@ -0,0 +1,78 @@ +@use '@tokens' as tokens; +@use '../../tools/breakpoint'; +@use '../../tools/responsive-properties'; +@use 'theme'; +@use 'tools'; + +.Skeleton { + width: 100%; +} + +.Skeleton--text { + @include tools.generate-item-sizes( + $class-name: 'Skeleton', + $sizes: tools.generate-text-sizes(theme.$sizes-body, 'mobile') + ); +} + +.Skeleton--heading { + @each $breakpoint-name, $breakpoint-value in theme.$breakpoints { + @include breakpoint.up($breakpoint-value) { + @include tools.generate-item-sizes( + $class-name: 'Skeleton', + $sizes: tools.generate-text-sizes(theme.$sizes-heading, $breakpoint-name) + ); + } + } +} + +.Skeleton--shape { + display: inline-flex; + flex-shrink: 0; + width: var(--#{tokens.$css-variable-prefix}skeleton-shape-width); + height: var(--#{tokens.$css-variable-prefix}skeleton-shape-height); + border: theme.$shape-border-width solid theme.$shape-border-color; + + @include responsive-properties.create-cascade( + $property: 'border-radius', + $input-custom-property-base-name: '#{tokens.$css-variable-prefix}skeleton-shape-radius', + $breakpoints: theme.$breakpoints, + $fallback-value: theme.$shape-default-border-radius + ); +} + +.Skeleton__item, +.Skeleton--shape { + background: theme.$item-gradient; + background-size: 600% 600%; + + @media (prefers-reduced-motion: no-preference) { + animation: skeleton-loading 2.5s infinite; + } +} + +.Skeleton--text > .Skeleton__item, +.Skeleton--heading > .Skeleton__item { + display: block; + width: 100%; + height: var(--#{tokens.$css-variable-prefix}skeleton-height, #{theme.$typography-default-height}); + border-radius: theme.$typography-border-radius; + + &:not(:last-child) { + margin-bottom: theme.$margin-bottom; + } + + &:last-child:not(:only-child) { + width: 80%; + } +} + +@keyframes skeleton-loading { + 0% { + background-position: 100% 50%; + } + + 100% { + background-position: 0 50%; + } +} diff --git a/packages/web/src/scss/components/Skeleton/_theme.scss b/packages/web/src/scss/components/Skeleton/_theme.scss new file mode 100644 index 0000000000..7010140293 --- /dev/null +++ b/packages/web/src/scss/components/Skeleton/_theme.scss @@ -0,0 +1,29 @@ +@use '@tokens' as tokens; +@use 'tools'; + +$margin-bottom: tokens.$space-400; +$item-gradient: tokens.$gradient-skeleton; +$breakpoints: tokens.$breakpoints; + +$typography-border-radius: tokens.$radius-300; +$typography-default-height: tools.get-height(tokens.$body-medium-bold, mobile); + +$shape-border-color: tokens.$disabled-border; +$shape-border-width: tokens.$border-width-100; +$shape-default-border-radius: tokens.$radius-300; + +$sizes-body: ( + xsmall: tokens.$body-xsmall-bold, + small: tokens.$body-small-bold, + medium: tokens.$body-medium-bold, + large: tokens.$body-large-bold, + xlarge: tokens.$body-xlarge-bold, +); + +$sizes-heading: ( + xsmall: tokens.$heading-xsmall-bold, + small: tokens.$heading-small-bold, + medium: tokens.$heading-medium-bold, + large: tokens.$heading-large-bold, + xlarge: tokens.$heading-xlarge-bold, +); diff --git a/packages/web/src/scss/components/Skeleton/_tools.scss b/packages/web/src/scss/components/Skeleton/_tools.scss new file mode 100644 index 0000000000..359ef73779 --- /dev/null +++ b/packages/web/src/scss/components/Skeleton/_tools.scss @@ -0,0 +1,36 @@ +@use 'sass:map'; +@use '@tokens' as tokens; +@use '../../tools/string' as spirit-string; + +@function get-height($token, $device) { + @return map.get($token, $device, font-size) * map.get($token, $device, line-height); +} + +@function generate-text-sizes($sizes, $device) { + $result: (); + + @each $size, $token in $sizes { + $height: get-height($token, $device); + $size-map: ( + #{$size}: + ( + height: $height, + ) + ); + $result: map.merge($result, $size-map); + } + + @return $result; +} + +@mixin generate-item-sizes($class-name, $sizes) { + @each $size, $variables in $sizes { + &.#{$class-name}--#{$size} { + $component-infix: spirit-string.convert-pascal-case-to-kebab-case($class-name); + + @each $variable-key, $variable-value in $variables { + --#{tokens.$css-variable-prefix}#{$component-infix}-#{$variable-key}: #{$variable-value}; + } + } + } +} diff --git a/packages/web/src/scss/components/Skeleton/index.html b/packages/web/src/scss/components/Skeleton/index.html new file mode 100644 index 0000000000..aab71f8309 --- /dev/null +++ b/packages/web/src/scss/components/Skeleton/index.html @@ -0,0 +1,169 @@ +{{#> web/layout/default title="Skeleton" parentPageName="Components" }} + +{{setVar "sizes" "xsmall" "small" "medium" "large" "xlarge" }} + +
+
+

SkeletonText

+
+
+ + {{#each @root.sizes as |size|}} +
+

Size {{size}}

+
+
+ + + +
+
+ + +
+
+
+ {{/each}} + +
+
+
+
+ +
+
+

SkeletonHeading

+
+
+ + {{#each @root.sizes as |size|}} +
+

Size {{size}}

+
+
+ + + +
+
+
+ {{/each}} + +
+
+
+
+ +
+
+

SkeletonShape

+
+
+ +
+

Shape Square

+
+
+
+
+
+ +
+

Shape Circle

+
+
+
+
+
+ +
+

Shape Rectangular

+
+
+
+
+
+ +
+

Shape Rectangular responsive radius

+
+
+
+
+
+ +
+
+
+
+ +
+
+

Combined Skeletons

+
+
+
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+
+
+
+
+ + +{{/web/layout/default }} diff --git a/packages/web/src/scss/components/Skeleton/index.scss b/packages/web/src/scss/components/Skeleton/index.scss new file mode 100644 index 0000000000..f44b6cf98b --- /dev/null +++ b/packages/web/src/scss/components/Skeleton/index.scss @@ -0,0 +1 @@ +@forward 'Skeleton'; diff --git a/packages/web/src/scss/components/index.scss b/packages/web/src/scss/components/index.scss index 13a2888365..417e9c0c67 100644 --- a/packages/web/src/scss/components/index.scss +++ b/packages/web/src/scss/components/index.scss @@ -23,6 +23,7 @@ @forward 'Radio'; @forward 'ScrollView'; @forward 'Select'; +@forward 'Skeleton'; @forward 'Stack'; @forward 'Tabs'; @forward 'Tag'; diff --git a/tests/e2e/demo-components-compare.spec.ts-snapshots/skeleton-chromium-linux.png b/tests/e2e/demo-components-compare.spec.ts-snapshots/skeleton-chromium-linux.png new file mode 100644 index 0000000000..8b9dac0f74 Binary files /dev/null and b/tests/e2e/demo-components-compare.spec.ts-snapshots/skeleton-chromium-linux.png differ diff --git a/tests/e2e/demo-homepages.spec.ts-snapshots/web-chromium-linux.png b/tests/e2e/demo-homepages.spec.ts-snapshots/web-chromium-linux.png index e6d8da3af5..845ef7640a 100644 Binary files a/tests/e2e/demo-homepages.spec.ts-snapshots/web-chromium-linux.png and b/tests/e2e/demo-homepages.spec.ts-snapshots/web-chromium-linux.png differ