-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
278 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"namesake": patch | ||
--- | ||
|
||
Improve loading state of quests with skeleton loader component |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import type { Meta } from "@storybook/react"; | ||
|
||
import { Skeleton, SkeletonCircle, SkeletonText } from "./Skeleton"; | ||
|
||
const meta = { | ||
component: Skeleton, | ||
parameters: { | ||
layout: "padded", | ||
}, | ||
} satisfies Meta<typeof Skeleton>; | ||
|
||
export default meta; | ||
|
||
export const Default = (args: any) => <Skeleton {...args} />; | ||
|
||
Default.args = { | ||
className: "w-48 h-24", | ||
}; | ||
|
||
export const Circle = (args: any) => <SkeletonCircle {...args} />; | ||
|
||
Circle.args = { | ||
className: "w-10 h-10", | ||
}; | ||
|
||
export const Text = (args: any) => <SkeletonText {...args} />; | ||
|
||
Text.args = { | ||
lines: 3, | ||
align: "left", | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { render, screen } from "@testing-library/react"; | ||
import { describe, expect, it } from "vitest"; | ||
import { Skeleton, SkeletonCircle, SkeletonText } from "./Skeleton"; | ||
|
||
describe("Skeleton", () => { | ||
it("should render base skeleton with correct attributes", () => { | ||
render(<Skeleton />); | ||
const skeleton = screen.getByTestId("skeleton"); | ||
|
||
expect(skeleton).toBeInTheDocument(); | ||
expect(skeleton).toHaveAttribute("aria-hidden", "true"); | ||
expect(skeleton.className).toContain("animate-fade-in"); | ||
}); | ||
|
||
it("should apply custom className to base skeleton", () => { | ||
render(<Skeleton className="custom-class" />); | ||
const skeleton = screen.getByTestId("skeleton"); | ||
|
||
expect(skeleton.className).toContain("custom-class"); | ||
}); | ||
}); | ||
|
||
describe("SkeletonCircle", () => { | ||
it("should render circle skeleton with correct attributes", () => { | ||
render(<SkeletonCircle />); | ||
const skeleton = screen.getByTestId("skeleton-circle"); | ||
|
||
expect(skeleton).toBeInTheDocument(); | ||
expect(skeleton).toHaveAttribute("aria-hidden", "true"); | ||
expect(skeleton.className).toContain("rounded-full"); | ||
}); | ||
|
||
it("should apply custom className to circle skeleton", () => { | ||
render(<SkeletonCircle className="custom-class" />); | ||
const skeleton = screen.getByTestId("skeleton-circle"); | ||
|
||
expect(skeleton.className).toContain("custom-class"); | ||
}); | ||
}); | ||
|
||
describe("SkeletonText", () => { | ||
it("should render text skeleton with default props", () => { | ||
render(<SkeletonText />); | ||
const container = screen.getByTestId("skeleton-text"); | ||
const skeletonLines = container.children; | ||
|
||
expect(container).toBeInTheDocument(); | ||
expect(container).toHaveAttribute("aria-hidden", "true"); | ||
expect(skeletonLines).toHaveLength(2); // default lines prop | ||
expect(container.className).toContain("items-start"); // default left alignment | ||
}); | ||
|
||
it("should render specified number of lines", () => { | ||
render(<SkeletonText lines={4} />); | ||
const container = screen.getByTestId("skeleton-text"); | ||
const skeletonLines = container.children; | ||
|
||
expect(skeletonLines).toHaveLength(4); | ||
}); | ||
|
||
it("should apply correct alignment classes", () => { | ||
render(<SkeletonText align="center" />); | ||
const container = screen.getByTestId("skeleton-text"); | ||
|
||
expect(container.className).toContain("items-center"); | ||
}); | ||
|
||
it("should generate random widths for each line", () => { | ||
render(<SkeletonText lines={3} />); | ||
const container = screen.getByTestId("skeleton-text"); | ||
const skeletonLines = Array.from(container.children); | ||
|
||
const widths = skeletonLines.map((line) => line.getAttribute("style")); | ||
// Check that each line has a width between 70% and 90% | ||
for (const width of widths) { | ||
const percentage = Number.parseInt(width?.match(/\d+/)?.[0] ?? "0"); | ||
expect(percentage).toBeGreaterThanOrEqual(70); | ||
expect(percentage).toBeLessThanOrEqual(90); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { tv } from "tailwind-variants"; | ||
|
||
const skeletonStyles = tv({ | ||
base: "w-full h-full relative animate-fade-in before:inset-0 before:absolute before:bg-graya-4 before:dark:bg-graydarka-4 before:rounded before:animate-pulse", | ||
variants: { | ||
type: { | ||
circle: "rounded-full", | ||
text: "h-4", | ||
}, | ||
}, | ||
}); | ||
|
||
type SkeletonProps = { | ||
className?: string; | ||
} & React.HTMLAttributes<HTMLDivElement>; | ||
|
||
export const Skeleton = ({ className }: SkeletonProps) => { | ||
return ( | ||
<div | ||
className={skeletonStyles({ className })} | ||
aria-hidden | ||
data-testid="skeleton" | ||
/> | ||
); | ||
}; | ||
|
||
export const SkeletonCircle = ({ className }: SkeletonProps) => { | ||
return ( | ||
<div | ||
className={skeletonStyles({ className, type: "circle" })} | ||
aria-hidden | ||
data-testid="skeleton-circle" | ||
/> | ||
); | ||
}; | ||
|
||
const skeletonParagraphStyles = tv({ | ||
base: "flex flex-col gap-2", | ||
variants: { | ||
align: { | ||
left: "items-start", | ||
center: "items-center", | ||
right: "items-end", | ||
}, | ||
}, | ||
}); | ||
|
||
type SkeletonTextProps = { | ||
className?: string; | ||
lines?: number; | ||
align?: "left" | "center" | "right"; | ||
} & React.HTMLAttributes<HTMLDivElement>; | ||
|
||
export const SkeletonText = ({ | ||
className, | ||
lines = 2, | ||
align = "left", | ||
}: SkeletonTextProps) => { | ||
return ( | ||
<div | ||
className={skeletonParagraphStyles({ className, align })} | ||
aria-hidden | ||
data-testid="skeleton-text" | ||
> | ||
{Array.from({ length: lines }).map((_, index) => { | ||
const minWidth = 70; | ||
const maxWidth = 90; | ||
const randomWidth = `${Math.floor(Math.random() * (maxWidth - minWidth + 1)) + minWidth}%`; | ||
|
||
return ( | ||
<div | ||
// biome-ignore lint/suspicious/noArrayIndexKey: Fine | ||
key={index} | ||
className={skeletonStyles({ type: "text", className })} | ||
style={{ width: randomWidth }} | ||
/> | ||
); | ||
})} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters