Skip to content

Commit 4485fc0

Browse files
Feat: Add Skeleton component (#381)
1 parent cae6729 commit 4485fc0

File tree

12 files changed

+468
-16
lines changed

12 files changed

+468
-16
lines changed

apps/www/src/app/examples/page.tsx

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Indicator,
2323
Sheet,
2424
EmptyState,
25+
Skeleton,
2526
} from "@raystack/apsara/v1";
2627
import React, { useState } from "react";
2728
import {
@@ -174,9 +175,8 @@ const Page = () => {
174175
required: true,
175176
selected: new Date()
176177
}}
177-
textFieldProps={{
178-
state: "valid",
179-
size: 'medium',
178+
inputFieldProps={{
179+
size: 'small',
180180
}}
181181
/>
182182

@@ -225,18 +225,12 @@ const Page = () => {
225225
}}
226226
/>
227227

228-
<DatePicker
229-
timeZone="UTC"
230-
dateFormat="DD MMM YYYY"
231-
onSelect={(date) => {
232-
console.log(date);
233-
}}
234-
>
235-
<InputField defaultValue="test" size="small" readOnly />
236-
</DatePicker>
237-
238-
<InputField defaultValue="test" size="small" readOnly />
239-
228+
<Text
229+
size="large"
230+
weight="medium"
231+
style={{ marginTop: "32px", marginBottom: "16px" }}>
232+
Skeleton Examples
233+
</Text>
240234

241235
<Text
242236
size="large"

apps/www/src/app/playground/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default function Page() {
4040
<Playground.SeparatorExamples />
4141
<Playground.SheetExamples />
4242
<Playground.SidebarExamples />
43+
<Playground.SkeletonExamples />
4344
<Playground.SliderExamples />
4445
<Playground.SpinnerExamples />
4546
<Playground.SwitchExamples />

apps/www/src/components/playground/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ export * from "./text-examples";
3939
export * from "./text-area-examples";
4040
export * from "./toast-examples";
4141
export * from "./tooltip-examples";
42+
export * from './skeleton-examples';
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"use client";
2+
3+
import { Skeleton, Flex } from "@raystack/apsara/v1";
4+
import PlaygroundLayout from "./playground-layout";
5+
6+
export function SkeletonExamples() {
7+
return (
8+
<PlaygroundLayout title="Skeleton">
9+
<Flex direction="column" gap="large">
10+
{/* Basic Usage */}
11+
<Skeleton width={200} height={20} />
12+
13+
{/* Multiple Lines */}
14+
<Skeleton width={200} height={20} count={3} />
15+
16+
{/* Custom Colors */}
17+
<Skeleton
18+
width={200}
19+
height={20}
20+
baseColor="var(--rs-color-background-accent-primary)"
21+
highlightColor="var(--rs-color-background-accent-emphasis)"
22+
/>
23+
24+
{/* Animation Control */}
25+
<Flex direction="column" gap="medium">
26+
<Skeleton width={200} height={20} duration={2.5} />
27+
<Skeleton width={200} height={20} enableAnimation={false} />
28+
</Flex>
29+
30+
{/* Card Layout Example */}
31+
<Flex direction="column" gap="medium" style={{ width: '300px' }}>
32+
<Skeleton height={200} /> {/* Image placeholder */}
33+
<Skeleton height={20} width="80%" /> {/* Title placeholder */}
34+
<Skeleton height={15} count={3} /> {/* Text lines placeholder */}
35+
</Flex>
36+
</Flex>
37+
</PlaygroundLayout>
38+
);
39+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"use client";
2+
3+
import { getPropsString } from "@/lib/utils";
4+
5+
export const getCode = (props: any) => {
6+
return `<Skeleton${getPropsString(props)} />`;
7+
};
8+
9+
export const playground = {
10+
type: "playground",
11+
controls: {
12+
width: {
13+
type: "text",
14+
initialValue: "200px",
15+
},
16+
height: {
17+
type: "text",
18+
initialValue: "15px",
19+
},
20+
count: {
21+
type: "number",
22+
initialValue: 3,
23+
},
24+
enableAnimation: {
25+
type: "checkbox",
26+
initialValue: true,
27+
},
28+
duration: {
29+
type: "number",
30+
initialValue: 1.5,
31+
},
32+
inline: {
33+
type: "checkbox",
34+
initialValue: false,
35+
}
36+
},
37+
getCode,
38+
};
39+
40+
export const basicDemo = {
41+
type: "code",
42+
code: `
43+
<Flex direction="column" gap="medium">
44+
<Skeleton width={200} height={15} />
45+
</Flex>`,
46+
};
47+
48+
export const multipleDemo = {
49+
type: "code",
50+
code: `
51+
<Flex direction="column" gap="medium">
52+
<Skeleton width={200} height={15} count={3} />
53+
</Flex>`,
54+
};
55+
56+
export const customStylesDemo = {
57+
type: "code",
58+
code: `
59+
<Flex direction="column" gap="medium">
60+
<Skeleton
61+
width={200}
62+
height={20}
63+
baseColor="var(--rs-color-background-accent-primary)"
64+
highlightColor="var(--rs-color-background-accent-emphasis)"
65+
/>
66+
</Flex>`,
67+
};
68+
69+
export const animationDemo = {
70+
type: "code",
71+
code: `
72+
<Flex direction="column" gap="medium">
73+
<Skeleton width={200} height={20} duration={2.5} />
74+
<Skeleton width={200} height={20} enableAnimation={false} />
75+
</Flex>`,
76+
};
77+
78+
export const cardDemo = {
79+
type: "code",
80+
code: `
81+
<Flex direction="column" gap="medium" style={{ width: '300px' }}>
82+
<Skeleton height={200} /> {/* Image placeholder */}
83+
<Skeleton height={20} width="80%" /> {/* Title placeholder */}
84+
<Skeleton height={15} count={3} /> {/* Text lines placeholder */}
85+
</Flex>`,
86+
};
87+
88+
export const providerDemo = {
89+
type: "code",
90+
code: `
91+
<Flex direction="column" gap="medium">
92+
<Skeleton.Provider
93+
height="24px"
94+
duration={2}
95+
>
96+
<Flex gap={4}>
97+
<Skeleton width="48px" height="48px" borderRadius="50%" />
98+
<Flex direction="column" gap={2} style={{ flex: 1 }}>
99+
<Skeleton width="200px" />
100+
<Skeleton width="150px" />
101+
</Flex>
102+
</Flex>
103+
</Skeleton.Provider>
104+
</Flex>`,
105+
};
106+
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
title: Skeleton
3+
description: A placeholder loading state that mimics the layout of content while it's being loaded.
4+
tag: new
5+
---
6+
7+
import {
8+
playground,
9+
basicDemo,
10+
multipleDemo,
11+
customStylesDemo,
12+
animationDemo,
13+
cardDemo,
14+
providerDemo,
15+
} from "./demo.ts";
16+
17+
<Demo data={playground} />
18+
19+
## Usage
20+
21+
The Skeleton component is used to create placeholder loading states that represent the layout of content while it's being loaded. It provides a visual indication that content is loading and helps maintain layout stability.
22+
23+
```tsx
24+
import { Skeleton } from "@raystack/apsara/v1";
25+
26+
<Skeleton width={200} height={20} />;
27+
```
28+
29+
## Skeleton Props
30+
31+
<auto-type-table path="./props.ts" name="SkeletonProps" />
32+
33+
## Examples
34+
35+
### Basic Usage
36+
37+
A simple skeleton loader with fixed dimensions.
38+
39+
<Demo data={basicDemo} />
40+
41+
### Multiple Lines
42+
43+
Create multiple skeleton lines using the count prop.
44+
45+
<Demo data={multipleDemo} />
46+
47+
### Using Provider
48+
49+
Group multiple skeletons and share common props using Skeleton.Provider.
50+
51+
<Demo data={providerDemo} />
52+
53+
### Custom Styles
54+
55+
Customize the appearance using baseColor and highlightColor.
56+
57+
<Demo data={customStylesDemo} />
58+
59+
### Animation Control
60+
61+
Control the animation duration or disable it entirely.
62+
63+
<Demo data={animationDemo} />
64+
65+
### Complex Layout
66+
67+
Create a card-like loading state by combining multiple skeletons.
68+
69+
<Demo data={cardDemo} />
70+
71+
## Provider Pattern
72+
73+
The Skeleton component supports a Provider pattern that allows you to share common props across multiple skeleton instances:
74+
75+
```tsx
76+
<Skeleton.Provider
77+
baseColor="var(--rs-color-background-base-secondary)"
78+
height="24px"
79+
duration={2}
80+
>
81+
<Skeleton /> {/* Inherits provider props */}
82+
<Skeleton width="75%" /> {/* Inherits provider props, overrides width */}
83+
<Skeleton
84+
height="48px"
85+
borderRadius="50%"
86+
/> {/* Overrides specific props */}
87+
</Skeleton.Provider>
88+
```
89+
90+
All props available on the Skeleton component can be passed to the Provider and will be inherited by child Skeleton components. Individual Skeleton components can override any provider props with their own values.
91+
92+
## Accessibility
93+
94+
The Skeleton component follows accessibility best practices:
95+
96+
- Uses semantic HTML elements
97+
- Provides appropriate ARIA attributes
98+
- Maintains sufficient color contrast
99+
- Animation can be disabled for users who prefer reduced motion
100+
- Supports both block and inline layouts
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
export interface SkeletonProps {
2+
/**
3+
* Width of the skeleton
4+
* @defaultValue "50px for inline, 100% otherwise"
5+
*/
6+
width?: string | number;
7+
8+
/**
9+
* Height of the skeleton
10+
* @defaultValue "var(--rs-space-4)"
11+
*/
12+
height?: string | number;
13+
14+
/**
15+
* Base color of the skeleton
16+
* @defaultValue "var(--rs-color-background-base-primary-hover)"
17+
*/
18+
baseColor?: string;
19+
20+
/**
21+
* Color of the shimmer effect
22+
* @defaultValue "var(--rs-color-background-base-primary)"
23+
*/
24+
highlightColor?: string;
25+
26+
/**
27+
* Border radius of the skeleton
28+
* @defaultValue "var(--rs-radius-2)"
29+
*/
30+
borderRadius?: string | number;
31+
32+
/**
33+
* Whether to display as inline-block
34+
* @defaultValue false
35+
*/
36+
inline?: boolean;
37+
38+
/**
39+
* Duration of the animation in seconds
40+
* @defaultValue 1.5
41+
*/
42+
duration?: number;
43+
44+
/**
45+
* Whether to enable the shimmer animation
46+
* @defaultValue true
47+
*/
48+
enableAnimation?: boolean;
49+
50+
/**
51+
* Number of skeleton elements to render
52+
* @defaultValue 1
53+
*/
54+
count?: number;
55+
56+
/**
57+
* Additional CSS class names for the skeleton element
58+
*/
59+
className?: string;
60+
61+
/**
62+
* Additional inline styles for the skeleton element
63+
*/
64+
style?: React.CSSProperties;
65+
66+
/**
67+
* Additional CSS class names for the container element (div for block and span for inline)
68+
*/
69+
containerClassName?: string;
70+
71+
/**
72+
* Additional inline styles for the container element (div for block and span for inline)
73+
*/
74+
containerStyle?: React.CSSProperties;
75+
}

packages/raystack/v1/components/filter-chip/filter-chip.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export const FilterChip = ({
154154
value={filterValue}
155155
onSelect={date => setFilterValue(date)}
156156
showCalendarIcon={false}
157-
textFieldProps={{ className: styles.dateField }}
157+
inputFieldProps={{ className: styles.dateField }}
158158
/>
159159
</div>
160160
);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Skeleton } from './skeleton';

0 commit comments

Comments
 (0)