@bspk/ui 1.1.11 → 1.1.13

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.
@@ -15,6 +15,12 @@ export type EmptyStateProps = {
15
15
  * @required
16
16
  */
17
17
  body: string;
18
+ /**
19
+ * The text alignment of the body.
20
+ *
21
+ * @default center
22
+ */
23
+ bodyAlign?: 'center' | 'left' | 'right';
18
24
  /**
19
25
  * This property may be undefined or an object containing required CallToActionButton properties.
20
26
  *
@@ -27,7 +33,7 @@ export type EmptyStateProps = {
27
33
  *
28
34
  * @name EmptyState
29
35
  */
30
- declare function EmptyState({ children, header, body, callToAction }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
36
+ declare function EmptyState({ children, header, body, callToAction, bodyAlign }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
31
37
  declare namespace EmptyState {
32
38
  var bspkName: string;
33
39
  }
@@ -7,11 +7,11 @@ import { Txt } from './Txt';
7
7
  *
8
8
  * @name EmptyState
9
9
  */
10
- function EmptyState({ children, header, body, callToAction }) {
10
+ function EmptyState({ children, header, body, callToAction, bodyAlign = 'center' }) {
11
11
  return (_jsxs(Layout, { align: "center", column: true, "data-bspk": "empty-state", style: {
12
12
  margin: 'var(--spacing-sizing-04)',
13
13
  maxWidth: '500px',
14
- }, children: [children, _jsxs(Layout, { align: "center", column: true, gap: "4", children: [_jsx(Txt, { as: "div", variant: "heading-h5", children: header }), _jsx(Txt, { as: "div", variant: "body-base", children: body })] }), callToAction && (_jsx(Button, { label: callToAction.label, onClick: callToAction.onClick, size: callToAction.size || 'medium', variant: "primary" }))] }));
14
+ }, children: [children, _jsxs(Layout, { align: "center", column: true, gap: "4", children: [_jsx(Txt, { as: "header", variant: "heading-h5", children: header }), _jsx(Txt, { as: "p", style: { textAlign: bodyAlign }, variant: "body-base", children: body })] }), callToAction && (_jsx(Button, { label: callToAction.label, onClick: callToAction.onClick, size: callToAction.size || 'medium', variant: "primary" }))] }));
15
15
  }
16
16
  EmptyState.bspkName = 'EmptyState';
17
17
  export { EmptyState };
@@ -1 +1 @@
1
- {"version":3,"file":"EmptyState.js","sourceRoot":"","sources":["../src/EmptyState.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AA2B5B;;;;GAIG;AACH,SAAS,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAmB;IACzE,OAAO,CACH,MAAC,MAAM,IACH,KAAK,EAAC,QAAQ,EACd,MAAM,qBACI,aAAa,EACvB,KAAK,EAAE;YACH,MAAM,EAAE,0BAA0B;YAClC,QAAQ,EAAE,OAAO;SACpB,aAEA,QAAQ,EACT,MAAC,MAAM,IAAC,KAAK,EAAC,QAAQ,EAAC,MAAM,QAAC,GAAG,EAAC,GAAG,aACjC,KAAC,GAAG,IAAC,EAAE,EAAC,KAAK,EAAC,OAAO,EAAC,YAAY,YAC7B,MAAM,GACL,EACN,KAAC,GAAG,IAAC,EAAE,EAAC,KAAK,EAAC,OAAO,EAAC,WAAW,YAC5B,IAAI,GACH,IACD,EACR,YAAY,IAAI,CACb,KAAC,MAAM,IACH,KAAK,EAAE,YAAY,CAAC,KAAK,EACzB,OAAO,EAAE,YAAY,CAAC,OAAO,EAC7B,IAAI,EAAE,YAAY,CAAC,IAAI,IAAI,QAAQ,EACnC,OAAO,EAAC,SAAS,GACnB,CACL,IACI,CACZ,CAAC;AACN,CAAC;AAED,UAAU,CAAC,QAAQ,GAAG,YAAY,CAAC;AAEnC,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"EmptyState.js","sourceRoot":"","sources":["../src/EmptyState.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAiC5B;;;;GAIG;AACH,SAAS,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,GAAG,QAAQ,EAAmB;IAC/F,OAAO,CACH,MAAC,MAAM,IACH,KAAK,EAAC,QAAQ,EACd,MAAM,qBACI,aAAa,EACvB,KAAK,EAAE;YACH,MAAM,EAAE,0BAA0B;YAClC,QAAQ,EAAE,OAAO;SACpB,aAEA,QAAQ,EACT,MAAC,MAAM,IAAC,KAAK,EAAC,QAAQ,EAAC,MAAM,QAAC,GAAG,EAAC,GAAG,aACjC,KAAC,GAAG,IAAC,EAAE,EAAC,QAAQ,EAAC,OAAO,EAAC,YAAY,YAChC,MAAM,GACL,EACN,KAAC,GAAG,IAAC,EAAE,EAAC,GAAG,EAAC,KAAK,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,OAAO,EAAC,WAAW,YAC3D,IAAI,GACH,IACD,EACR,YAAY,IAAI,CACb,KAAC,MAAM,IACH,KAAK,EAAE,YAAY,CAAC,KAAK,EACzB,OAAO,EAAE,YAAY,CAAC,OAAO,EAC7B,IAAI,EAAE,YAAY,CAAC,IAAI,IAAI,QAAQ,EACnC,OAAO,EAAC,SAAS,GACnB,CACL,IACI,CACZ,CAAC;AACN,CAAC;AAED,UAAU,CAAC,QAAQ,GAAG,YAAY,CAAC;AAEnC,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -1,26 +1,65 @@
1
1
  import './skeleton.scss';
2
2
  import { TxtVariant } from './utils/txtVariants';
3
- export type SkeletonVariant = TxtVariant | 'other';
4
3
  export type SkeletonProps = {
5
4
  /**
6
- * The text variant of the skeleton. If 'other' skeleton will expand to size of nearest relative positioned parent.
5
+ * The variant of the skeleton that best hints the content being loaded.
7
6
  *
8
- * @default other
7
+ * @default text
9
8
  */
10
- variant?: SkeletonVariant;
9
+ variant?: 'circular' | 'photo' | 'profile' | 'rectangular' | 'text' | 'thumbnail';
11
10
  /**
12
- * The number of lines showing. Ignored when variant is other.
11
+ * The variant of the text being loaded. This is only used when variant is 'text'.
13
12
  *
14
- * @default 1
13
+ * @default body-base
15
14
  */
16
- lines?: number;
15
+ textVariant?: TxtVariant;
16
+ /**
17
+ * The number of lines showing. This is only used when variant is 'text'.
18
+ *
19
+ * @default 3
20
+ */
21
+ textLines?: number;
22
+ /**
23
+ * The width of the skeleton. This is ignored when variant is 'text', 'profile', or 'thumbnail'.
24
+ *
25
+ * @default 200
26
+ */
27
+ width?: number | string;
28
+ /**
29
+ * The height of the skeleton. This is ignored when variant is 'text', 'profile', or 'thumbnail'.
30
+ *
31
+ * @default 100
32
+ */
33
+ height?: number | string;
17
34
  };
18
35
  /**
19
36
  * A visual placeholder for an element while it is in a loading state.
20
37
  *
38
+ * The data for your components might not be immediately available. You can improve the perceived responsiveness of the
39
+ * page by using skeletons. It feels like things are happening immediately, then the information is incrementally
40
+ * displayed on the screen.
41
+ *
42
+ * @example
43
+ * function Example() {
44
+ * return item ? (
45
+ * <img
46
+ * style={{
47
+ * width: 210,
48
+ * height: 118,
49
+ * }}
50
+ * alt={item.title}
51
+ * src={item.src}
52
+ * />
53
+ * ) : (
54
+ * <Skeleton variant="photo" width={210} height={118} />
55
+ * );
56
+ * }
57
+ *
58
+ * @exampleDescription This example shows a skeleton loading state for an image but can be used for any element.
59
+ *
21
60
  * @name Skeleton
22
61
  */
23
- declare function Skeleton({ variant, lines: linesProp }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
62
+ declare function Skeleton({ width, height, textLines, textVariant: textSize, variant, }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
24
63
  declare namespace Skeleton {
25
64
  var bspkName: string;
26
65
  }
package/dist/Skeleton.js CHANGED
@@ -1,20 +1,46 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { styleAdd } from './utils/styleAdd';
3
3
  styleAdd(`[data-bspk=skeleton]{/*!
4
- --margin: is set via inline style
4
+ --text-margin: is set via inline style
5
+ --text-height: is set via inline style
5
6
  --height: is set via inline style
6
- */width:100%;display:flex;flex-direction:column;gap:var(--margin);animation:skeleton-pulse 1.5s infinite}@keyframes skeleton-pulse{0%{opacity:.8}50%{opacity:.4}100%{opacity:.8}}[data-bspk=skeleton]:empty{width:auto;height:auto;position:absolute;inset:0;background:var(--foreground-neutral-skeleton-element);border-radius:var(--radius-small)}[data-bspk=skeleton] [data-line]{width:100%;background:var(--foreground-neutral-skeleton-element);border-radius:var(--radius-small);height:var(--height)}`);;
7
+ --width: is set via inline style
8
+ */display:flex;flex-direction:column;gap:var(--text-margin);animation:skeleton-pulse 1.5s infinite;background:var(--foreground-neutral-skeleton-element)}@keyframes skeleton-pulse{0%{opacity:.8}50%{opacity:.4}100%{opacity:.8}}[data-bspk=skeleton][data-variant=rectangular],[data-bspk=skeleton][data-variant=photo]{min-width:var(--spacing-sizing-08);min-height:var(--spacing-sizing-08);height:var(--height, var(--width));width:var(--width, var(--height));border-radius:var(--radius-small)}[data-bspk=skeleton][data-variant=photo]{border-radius:var(--radius-medium)}[data-bspk=skeleton][data-variant=circular]{border-radius:100%;width:var(--width);aspect-ratio:1/1}[data-bspk=skeleton][data-variant=profile]{border-radius:100%;width:var(--spacing-sizing-10);aspect-ratio:1/1}[data-bspk=skeleton][data-variant=thumbnail]{width:var(--spacing-sizing-12);height:var(--spacing-sizing-12);border-radius:var(--radius-small)}[data-bspk=skeleton][data-variant=text]{background:rgba(0,0,0,0);min-height:unset;max-height:unset;width:100%;height:fit-content}[data-bspk=skeleton][data-variant=text] [data-line]{width:100%;background:var(--foreground-neutral-skeleton-element);border-radius:var(--radius-small);height:var(--text-height)}[data-bspk=skeleton][data-variant=text]:has([data-line]:nth-child(2)) [data-line]:last-child{width:80%}`);;
7
9
  /**
8
10
  * A visual placeholder for an element while it is in a loading state.
9
11
  *
12
+ * The data for your components might not be immediately available. You can improve the perceived responsiveness of the
13
+ * page by using skeletons. It feels like things are happening immediately, then the information is incrementally
14
+ * displayed on the screen.
15
+ *
16
+ * @example
17
+ * function Example() {
18
+ * return item ? (
19
+ * <img
20
+ * style={{
21
+ * width: 210,
22
+ * height: 118,
23
+ * }}
24
+ * alt={item.title}
25
+ * src={item.src}
26
+ * />
27
+ * ) : (
28
+ * <Skeleton variant="photo" width={210} height={118} />
29
+ * );
30
+ * }
31
+ *
32
+ * @exampleDescription This example shows a skeleton loading state for an image but can be used for any element.
33
+ *
10
34
  * @name Skeleton
11
35
  */
12
- function Skeleton({ variant = 'other', lines: linesProp = 3 }) {
13
- const lines = Math.max(1, linesProp || 0);
14
- return (_jsx("div", { "data-bspk": "skeleton", style: {
15
- '--margin': `calc(var(--${variant}-line-height) - var(--${variant}-size))`,
16
- '--height': `var(--${variant}-size)`,
17
- }, children: variant !== 'other' && [...Array(lines)].map((_, index) => _jsx("div", { "data-line": true }, index)) }));
36
+ function Skeleton({ width = 100, height = 100, textLines, textVariant: textSize, variant = 'rectangular', }) {
37
+ return (_jsx("div", { "data-bspk": "skeleton", "data-variant": variant, style: {
38
+ '--height': typeof height === 'number' ? `${height}px` : height,
39
+ '--text-height': `var(--${textSize}-size)`,
40
+ '--text-margin': `calc(var(--${textSize}-line-height) - var(--${textSize}-size))`,
41
+ '--width': typeof width === 'number' ? `${width}px` : width,
42
+ }, children: variant === 'text' &&
43
+ [...Array(Math.max(1, textLines || 0))].map((_, index) => _jsx("div", { "data-line": true }, index)) }));
18
44
  }
19
45
  Skeleton.bspkName = 'Skeleton';
20
46
  export { Skeleton };
@@ -1 +1 @@
1
- {"version":3,"file":"Skeleton.js","sourceRoot":"","sources":["../src/Skeleton.tsx"],"names":[],"mappings":";AAAA,OAAO,iBAAiB,CAAC;AAsBzB;;;;GAIG;AACH,SAAS,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,CAAC,EAAiB;IACxE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,IAAI,CAAC,CAAC,CAAC;IAE1C,OAAO,CACH,2BACc,UAAU,EACpB,KAAK,EACD;YACI,UAAU,EAAE,cAAc,OAAO,yBAAyB,OAAO,SAAS;YAC1E,UAAU,EAAE,SAAS,OAAO,QAAQ;SACtB,YAGrB,OAAO,KAAK,OAAO,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,mCAAoB,KAAK,CAAI,CAAC,GACxF,CACT,CAAC;AACN,CAAC;AAED,QAAQ,CAAC,QAAQ,GAAG,UAAU,CAAC;AAE/B,OAAO,EAAE,QAAQ,EAAE,CAAC"}
1
+ {"version":3,"file":"Skeleton.js","sourceRoot":"","sources":["../src/Skeleton.tsx"],"names":[],"mappings":";AAAA,OAAO,iBAAiB,CAAC;AAsCzB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAS,QAAQ,CAAC,EACd,KAAK,GAAG,GAAG,EACX,MAAM,GAAG,GAAG,EACZ,SAAS,EACT,WAAW,EAAE,QAAQ,EACrB,OAAO,GAAG,aAAa,GACX;IACZ,OAAO,CACH,2BACc,UAAU,kBACN,OAAO,EACrB,KAAK,EACD;YACI,UAAU,EAAE,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM;YAC/D,eAAe,EAAE,SAAS,QAAQ,QAAQ;YAC1C,eAAe,EAAE,cAAc,QAAQ,yBAAyB,QAAQ,SAAS;YACjF,SAAS,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK;SAC7C,YAGrB,OAAO,KAAK,MAAM;YACf,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,mCAAoB,KAAK,CAAI,CAAC,GAC1F,CACT,CAAC;AACN,CAAC;AAED,QAAQ,CAAC,QAAQ,GAAG,UAAU,CAAC;AAE/B,OAAO,EAAE,QAAQ,EAAE,CAAC"}
package/dist/skeleton.css CHANGED
@@ -1,4 +1,6 @@
1
1
  [data-bspk=skeleton]{/*!
2
- --margin: is set via inline style
2
+ --text-margin: is set via inline style
3
+ --text-height: is set via inline style
3
4
  --height: is set via inline style
4
- */width:100%;display:flex;flex-direction:column;gap:var(--margin);animation:skeleton-pulse 1.5s infinite}@keyframes skeleton-pulse{0%{opacity:.8}50%{opacity:.4}100%{opacity:.8}}[data-bspk=skeleton]:empty{width:auto;height:auto;position:absolute;inset:0;background:var(--foreground-neutral-skeleton-element);border-radius:var(--radius-small)}[data-bspk=skeleton] [data-line]{width:100%;background:var(--foreground-neutral-skeleton-element);border-radius:var(--radius-small);height:var(--height)}
5
+ --width: is set via inline style
6
+ */display:flex;flex-direction:column;gap:var(--text-margin);animation:skeleton-pulse 1.5s infinite;background:var(--foreground-neutral-skeleton-element)}@keyframes skeleton-pulse{0%{opacity:.8}50%{opacity:.4}100%{opacity:.8}}[data-bspk=skeleton][data-variant=rectangular],[data-bspk=skeleton][data-variant=photo]{min-width:var(--spacing-sizing-08);min-height:var(--spacing-sizing-08);height:var(--height, var(--width));width:var(--width, var(--height));border-radius:var(--radius-small)}[data-bspk=skeleton][data-variant=photo]{border-radius:var(--radius-medium)}[data-bspk=skeleton][data-variant=circular]{border-radius:100%;width:var(--width);aspect-ratio:1/1}[data-bspk=skeleton][data-variant=profile]{border-radius:100%;width:var(--spacing-sizing-10);aspect-ratio:1/1}[data-bspk=skeleton][data-variant=thumbnail]{width:var(--spacing-sizing-12);height:var(--spacing-sizing-12);border-radius:var(--radius-small)}[data-bspk=skeleton][data-variant=text]{background:rgba(0,0,0,0);min-height:unset;max-height:unset;width:100%;height:fit-content}[data-bspk=skeleton][data-variant=text] [data-line]{width:100%;background:var(--foreground-neutral-skeleton-element);border-radius:var(--radius-small);height:var(--text-height)}[data-bspk=skeleton][data-variant=text]:has([data-line]:nth-child(2)) [data-line]:last-child{width:80%}
package/meta-types.ts CHANGED
@@ -6,11 +6,11 @@ export type BaseMeta = {
6
6
  name: string;
7
7
  description?: string;
8
8
  file?: string;
9
+ example?: string;
9
10
  };
10
11
 
11
12
  export type TypeMeta = BaseMeta & {
12
13
  id: string;
13
- example?: string;
14
14
  references?: string[];
15
15
  properties?: TypeProperty[];
16
16
  };
@@ -35,12 +35,15 @@ export type ComponentMeta = BaseMeta & {
35
35
  modified: string;
36
36
  css: string;
37
37
  hasTouchTarget: boolean;
38
+ usage?: {
39
+ code: string;
40
+ description?: string;
41
+ };
38
42
  };
39
43
 
40
44
  export type UtilityMeta = BaseMeta & {
41
45
  param?: string;
42
46
  returns?: string;
43
- example?: string;
44
47
  };
45
48
 
46
49
  /** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
package/meta.ts CHANGED
@@ -169,6 +169,13 @@ function generateComponentMeta({
169
169
 
170
170
  const css = fs.existsSync(cssPath) ? fs.readFileSync(cssPath, { encoding: 'utf-8' }) : '';
171
171
 
172
+ const usage = componentDoc.example
173
+ ? {
174
+ code: componentDoc.example,
175
+ description: componentDoc.exampleDescription,
176
+ }
177
+ : undefined;
178
+
172
179
  return {
173
180
  description: componentDoc.description,
174
181
  file: componentFile.split(componentsDir)[1],
@@ -176,6 +183,7 @@ function generateComponentMeta({
176
183
  slug,
177
184
  dependencies,
178
185
  modified: stats.mtime.toISOString(),
186
+ usage,
179
187
  css,
180
188
  hasTouchTarget: css.includes('data-touch-target'),
181
189
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bspk/ui",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "license": "CC-BY-4.0",
5
5
  "type": "module",
6
6
  "files": [
@@ -21,6 +21,12 @@ export type EmptyStateProps = {
21
21
  * @required
22
22
  */
23
23
  body: string;
24
+ /**
25
+ * The text alignment of the body.
26
+ *
27
+ * @default center
28
+ */
29
+ bodyAlign?: 'center' | 'left' | 'right';
24
30
  /**
25
31
  * This property may be undefined or an object containing required CallToActionButton properties.
26
32
  *
@@ -34,7 +40,7 @@ export type EmptyStateProps = {
34
40
  *
35
41
  * @name EmptyState
36
42
  */
37
- function EmptyState({ children, header, body, callToAction }: EmptyStateProps) {
43
+ function EmptyState({ children, header, body, callToAction, bodyAlign = 'center' }: EmptyStateProps) {
38
44
  return (
39
45
  <Layout
40
46
  align="center"
@@ -47,10 +53,10 @@ function EmptyState({ children, header, body, callToAction }: EmptyStateProps) {
47
53
  >
48
54
  {children}
49
55
  <Layout align="center" column gap="4">
50
- <Txt as="div" variant="heading-h5">
56
+ <Txt as="header" variant="heading-h5">
51
57
  {header}
52
58
  </Txt>
53
- <Txt as="div" variant="body-base">
59
+ <Txt as="p" style={{ textAlign: bodyAlign }} variant="body-base">
54
60
  {body}
55
61
  </Txt>
56
62
  </Layout>
package/src/Skeleton.tsx CHANGED
@@ -3,42 +3,88 @@ import { CSSProperties } from 'react';
3
3
 
4
4
  import { TxtVariant } from './utils/txtVariants';
5
5
 
6
- export type SkeletonVariant = TxtVariant | 'other';
7
-
8
6
  export type SkeletonProps = {
9
7
  /**
10
- * The text variant of the skeleton. If 'other' skeleton will expand to size of nearest relative positioned parent.
8
+ * The variant of the skeleton that best hints the content being loaded.
9
+ *
10
+ * @default text
11
+ */
12
+ variant?: 'circular' | 'photo' | 'profile' | 'rectangular' | 'text' | 'thumbnail';
13
+ /**
14
+ * The variant of the text being loaded. This is only used when variant is 'text'.
15
+ *
16
+ * @default body-base
17
+ */
18
+ textVariant?: TxtVariant;
19
+ /**
20
+ * The number of lines showing. This is only used when variant is 'text'.
11
21
  *
12
- * @default other
22
+ * @default 3
13
23
  */
14
- variant?: SkeletonVariant;
24
+ textLines?: number;
15
25
  /**
16
- * The number of lines showing. Ignored when variant is other.
26
+ * The width of the skeleton. This is ignored when variant is 'text', 'profile', or 'thumbnail'.
17
27
  *
18
- * @default 1
28
+ * @default 200
19
29
  */
20
- lines?: number;
30
+ width?: number | string;
31
+ /**
32
+ * The height of the skeleton. This is ignored when variant is 'text', 'profile', or 'thumbnail'.
33
+ *
34
+ * @default 100
35
+ */
36
+ height?: number | string;
21
37
  };
22
38
 
23
39
  /**
24
40
  * A visual placeholder for an element while it is in a loading state.
25
41
  *
42
+ * The data for your components might not be immediately available. You can improve the perceived responsiveness of the
43
+ * page by using skeletons. It feels like things are happening immediately, then the information is incrementally
44
+ * displayed on the screen.
45
+ *
46
+ * @example
47
+ * function Example() {
48
+ * return item ? (
49
+ * <img
50
+ * style={{
51
+ * width: 210,
52
+ * height: 118,
53
+ * }}
54
+ * alt={item.title}
55
+ * src={item.src}
56
+ * />
57
+ * ) : (
58
+ * <Skeleton variant="photo" width={210} height={118} />
59
+ * );
60
+ * }
61
+ *
62
+ * @exampleDescription This example shows a skeleton loading state for an image but can be used for any element.
63
+ *
26
64
  * @name Skeleton
27
65
  */
28
- function Skeleton({ variant = 'other', lines: linesProp = 3 }: SkeletonProps) {
29
- const lines = Math.max(1, linesProp || 0);
30
-
66
+ function Skeleton({
67
+ width = 100,
68
+ height = 100,
69
+ textLines,
70
+ textVariant: textSize,
71
+ variant = 'rectangular',
72
+ }: SkeletonProps) {
31
73
  return (
32
74
  <div
33
75
  data-bspk="skeleton"
76
+ data-variant={variant}
34
77
  style={
35
78
  {
36
- '--margin': `calc(var(--${variant}-line-height) - var(--${variant}-size))`,
37
- '--height': `var(--${variant}-size)`,
79
+ '--height': typeof height === 'number' ? `${height}px` : height,
80
+ '--text-height': `var(--${textSize}-size)`,
81
+ '--text-margin': `calc(var(--${textSize}-line-height) - var(--${textSize}-size))`,
82
+ '--width': typeof width === 'number' ? `${width}px` : width,
38
83
  } as CSSProperties
39
84
  }
40
85
  >
41
- {variant !== 'other' && [...Array(lines)].map((_, index) => <div data-line key={index} />)}
86
+ {variant === 'text' &&
87
+ [...Array(Math.max(1, textLines || 0))].map((_, index) => <div data-line key={index} />)}
42
88
  </div>
43
89
  );
44
90
  }
package/src/skeleton.scss CHANGED
@@ -1,14 +1,16 @@
1
1
  [data-bspk='skeleton'] {
2
2
  /*!
3
- --margin: is set via inline style
3
+ --text-margin: is set via inline style
4
+ --text-height: is set via inline style
4
5
  --height: is set via inline style
6
+ --width: is set via inline style
5
7
  */
6
8
 
7
- width: 100%;
8
9
  display: flex;
9
10
  flex-direction: column;
10
- gap: var(--margin);
11
+ gap: var(--text-margin);
11
12
  animation: skeleton-pulse 1.5s infinite;
13
+ background: var(--foreground-neutral-skeleton-element);
12
14
 
13
15
  @keyframes skeleton-pulse {
14
16
  0% {
@@ -24,20 +26,57 @@
24
26
  }
25
27
  }
26
28
 
27
- &:empty {
28
- width: auto;
29
- height: auto;
30
- position: absolute;
31
- inset: 0;
32
- background: var(--foreground-neutral-skeleton-element);
29
+ &[data-variant='rectangular'],
30
+ &[data-variant='photo'] {
31
+ min-width: var(--spacing-sizing-08);
32
+ min-height: var(--spacing-sizing-08);
33
+ height: var(--height, var(--width));
34
+ width: var(--width, var(--height));
33
35
  border-radius: var(--radius-small);
34
36
  }
35
37
 
36
- [data-line] {
37
- width: 100%;
38
- background: var(--foreground-neutral-skeleton-element);
38
+ &[data-variant='photo'] {
39
+ border-radius: var(--radius-medium);
40
+ }
41
+
42
+ &[data-variant='circular'] {
43
+ border-radius: 100%;
44
+ width: var(--width);
45
+ aspect-ratio: 1/1;
46
+ }
47
+
48
+ &[data-variant='profile'] {
49
+ border-radius: 100%;
50
+ width: var(--spacing-sizing-10);
51
+ aspect-ratio: 1/1;
52
+ }
53
+
54
+ &[data-variant='thumbnail'] {
55
+ width: var(--spacing-sizing-12);
56
+ height: var(--spacing-sizing-12);
39
57
  border-radius: var(--radius-small);
40
- height: var(--height);
58
+ }
59
+
60
+ &[data-variant='text'] {
61
+ background: transparent;
62
+ min-height: unset;
63
+ max-height: unset;
64
+ width: 100%;
65
+ height: fit-content;
66
+
67
+ [data-line] {
68
+ width: 100%;
69
+ background: var(--foreground-neutral-skeleton-element);
70
+ border-radius: var(--radius-small);
71
+ height: var(--text-height);
72
+ }
73
+
74
+ // if there are 2 lines or more, make the last line 80% width
75
+ &:has([data-line]:nth-child(2)) {
76
+ [data-line]:last-child {
77
+ width: 80%;
78
+ }
79
+ }
41
80
  }
42
81
  }
43
82