mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
refactor(ui): redesign profile and table loading skeletons to mirror real layouts
This commit is contained in:
@@ -1,37 +1,102 @@
|
||||
import React from "react";
|
||||
|
||||
import { Card } from "@/components/shadcn/card/card";
|
||||
import { Skeleton } from "@/components/shadcn/skeleton/skeleton";
|
||||
|
||||
export const SkeletonTableInvitation = () => {
|
||||
const SkeletonTableRow = () => {
|
||||
return (
|
||||
<Card variant="base" padding="md" className="flex flex-col gap-4">
|
||||
{/* Table headers */}
|
||||
<div className="hidden gap-4 md:flex">
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-1/12" />
|
||||
</div>
|
||||
|
||||
{/* Table body */}
|
||||
<div className="flex flex-col gap-3">
|
||||
{[...Array(10)].map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col gap-4 md:flex-row md:items-center"
|
||||
>
|
||||
<Skeleton className="h-12 w-full md:w-2/12" />
|
||||
<Skeleton className="h-12 w-full md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-1/12" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<tr className="border-border-neutral-secondary border-b last:border-b-0">
|
||||
{/* Email */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-32 rounded" />
|
||||
</td>
|
||||
{/* State - text */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-16 rounded" />
|
||||
</td>
|
||||
{/* Role - text */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-20 rounded" />
|
||||
</td>
|
||||
{/* Inserted At - date */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
</td>
|
||||
{/* Expires At - date */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
</td>
|
||||
{/* Actions */}
|
||||
<td className="px-2 py-4">
|
||||
<Skeleton className="size-6 rounded" />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export const SkeletonTableInvitation = () => {
|
||||
const rows = 10;
|
||||
|
||||
return (
|
||||
<div className="border-border-neutral-secondary bg-bg-neutral-secondary flex w-full flex-col gap-4 overflow-hidden rounded-[14px] border p-4 shadow-sm">
|
||||
{/* Toolbar: Search + Total entries */}
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Search icon button */}
|
||||
<Skeleton className="size-10 rounded-md" />
|
||||
{/* Total entries */}
|
||||
<Skeleton className="h-4 w-28 rounded" />
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-border-neutral-secondary border-b">
|
||||
{/* Email */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-16 rounded" />
|
||||
</th>
|
||||
{/* State */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-12 rounded" />
|
||||
</th>
|
||||
{/* Role */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-12 rounded" />
|
||||
</th>
|
||||
{/* Inserted At */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
</th>
|
||||
{/* Expires At */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
</th>
|
||||
{/* Actions - empty header */}
|
||||
<th className="w-10 py-3" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Array.from({ length: rows }).map((_, i) => (
|
||||
<SkeletonTableRow key={i} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* Pagination */}
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
{/* Rows per page */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
<Skeleton className="h-9 w-16 rounded-md" />
|
||||
</div>
|
||||
{/* Page info + navigation */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
<div className="flex gap-1">
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,39 +1,94 @@
|
||||
import React from "react";
|
||||
|
||||
import { Card } from "@/components/shadcn/card/card";
|
||||
import { Skeleton } from "@/components/shadcn/skeleton/skeleton";
|
||||
|
||||
export const SkeletonTableGroups = () => {
|
||||
const SkeletonTableRow = () => {
|
||||
return (
|
||||
<Card variant="base" padding="md" className="flex flex-col gap-4">
|
||||
{/* Table headers */}
|
||||
<div className="hidden gap-4 md:flex">
|
||||
<Skeleton className="h-8 w-1/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-1/12" />
|
||||
<Skeleton className="h-8 w-1/12" />
|
||||
</div>
|
||||
|
||||
{/* Table body */}
|
||||
<div className="flex flex-col gap-3">
|
||||
{[...Array(3)].map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col gap-4 md:flex-row md:items-center"
|
||||
>
|
||||
<Skeleton className="h-12 w-full md:w-1/12" />
|
||||
<Skeleton className="h-12 w-full md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-1/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-1/12" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<tr className="border-border-neutral-secondary border-b last:border-b-0">
|
||||
{/* Name - text */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-32 rounded" />
|
||||
</td>
|
||||
{/* Providers - count in circle */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="size-8 rounded-full" />
|
||||
</td>
|
||||
{/* Roles - count in circle */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="size-8 rounded-full" />
|
||||
</td>
|
||||
{/* Added - date */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
</td>
|
||||
{/* Actions */}
|
||||
<td className="px-2 py-4">
|
||||
<Skeleton className="size-6 rounded" />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export const SkeletonTableGroups = () => {
|
||||
const rows = 10;
|
||||
|
||||
return (
|
||||
<div className="border-border-neutral-secondary bg-bg-neutral-secondary flex w-full flex-col gap-4 overflow-hidden rounded-[14px] border p-4 shadow-sm">
|
||||
{/* Toolbar: Search + Total entries */}
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Search icon button */}
|
||||
<Skeleton className="size-10 rounded-md" />
|
||||
{/* Total entries */}
|
||||
<Skeleton className="h-4 w-28 rounded" />
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-border-neutral-secondary border-b">
|
||||
{/* Name */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-12 rounded" />
|
||||
</th>
|
||||
{/* Providers */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-20 rounded" />
|
||||
</th>
|
||||
{/* Roles */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-16 rounded" />
|
||||
</th>
|
||||
{/* Added */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-14 rounded" />
|
||||
</th>
|
||||
{/* Actions - empty header */}
|
||||
<th className="w-10 py-3" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Array.from({ length: rows }).map((_, i) => (
|
||||
<SkeletonTableRow key={i} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* Pagination */}
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
{/* Rows per page */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
<Skeleton className="h-9 w-16 rounded-md" />
|
||||
</div>
|
||||
{/* Page info + navigation */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
<div className="flex gap-1">
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,37 +1,102 @@
|
||||
import React from "react";
|
||||
|
||||
import { Card } from "@/components/shadcn/card/card";
|
||||
import { Skeleton } from "@/components/shadcn/skeleton/skeleton";
|
||||
|
||||
export const SkeletonTableRoles = () => {
|
||||
const SkeletonTableRow = () => {
|
||||
return (
|
||||
<Card variant="base" padding="md" className="flex flex-col gap-4">
|
||||
{/* Table headers */}
|
||||
<div className="hidden gap-4 md:flex">
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-1/12" />
|
||||
</div>
|
||||
|
||||
{/* Table body */}
|
||||
<div className="flex flex-col gap-3">
|
||||
{[...Array(10)].map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col gap-4 md:flex-row md:items-center"
|
||||
>
|
||||
<Skeleton className="h-12 w-full md:w-2/12" />
|
||||
<Skeleton className="h-12 w-full md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-1/12" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<tr className="border-border-neutral-secondary border-b last:border-b-0">
|
||||
{/* Role - bold name text */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-32 rounded" />
|
||||
</td>
|
||||
{/* Users - count text */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-16 rounded" />
|
||||
</td>
|
||||
{/* Invitations - count text */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-20 rounded" />
|
||||
</td>
|
||||
{/* Permissions - state text */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-20 rounded" />
|
||||
</td>
|
||||
{/* Added - date */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
</td>
|
||||
{/* Actions */}
|
||||
<td className="px-2 py-4">
|
||||
<Skeleton className="size-6 rounded" />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export const SkeletonTableRoles = () => {
|
||||
const rows = 10;
|
||||
|
||||
return (
|
||||
<div className="border-border-neutral-secondary bg-bg-neutral-secondary flex w-full flex-col gap-4 overflow-hidden rounded-[14px] border p-4 shadow-sm">
|
||||
{/* Toolbar: Search + Total entries */}
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Search icon button */}
|
||||
{/* <Skeleton className="size-10 rounded-md" /> */}
|
||||
{/* Total entries */}
|
||||
<Skeleton className="h-4 w-28 rounded" />
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-border-neutral-secondary border-b">
|
||||
{/* Role */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-12 rounded" />
|
||||
</th>
|
||||
{/* Users */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-14 rounded" />
|
||||
</th>
|
||||
{/* Invitations */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-20 rounded" />
|
||||
</th>
|
||||
{/* Permissions */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
</th>
|
||||
{/* Added */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-14 rounded" />
|
||||
</th>
|
||||
{/* Actions - empty header */}
|
||||
<th className="w-10 py-3" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Array.from({ length: rows }).map((_, i) => (
|
||||
<SkeletonTableRow key={i} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* Pagination */}
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
{/* Rows per page */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
<Skeleton className="h-9 w-16 rounded-md" />
|
||||
</div>
|
||||
{/* Page info + navigation */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
<div className="flex gap-1">
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,74 +1,156 @@
|
||||
import { Card, CardContent, Skeleton } from "@/components/shadcn";
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Separator,
|
||||
Skeleton,
|
||||
} from "@/components/shadcn";
|
||||
|
||||
// Mirrors the real profile layout (UserBasicInfoCard full-width + a two-column
|
||||
// grid with RolesCard and Organizations) so the page does not shift when the
|
||||
// data resolves. Conditional cards (SAML / API keys) depend on permissions not
|
||||
// known while loading, so we mirror only the always-present structure.
|
||||
export const SkeletonUserInfo = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* User Information */}
|
||||
<Card>
|
||||
<CardContent className="p-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Name */}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-text-neutral-secondary text-sm font-semibold">
|
||||
Name:
|
||||
</p>
|
||||
<Skeleton className="h-5 w-24 rounded-lg" />
|
||||
</div>
|
||||
{/* Email */}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-text-neutral-secondary text-sm font-semibold">
|
||||
Email:
|
||||
</p>
|
||||
<Skeleton className="h-5 w-32 rounded-lg" />
|
||||
</div>
|
||||
{/* Company */}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-text-neutral-secondary text-sm font-semibold">
|
||||
Company:
|
||||
</p>
|
||||
<Skeleton className="h-5 w-28 rounded-lg" />
|
||||
</div>
|
||||
{/* Date Joined */}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-text-neutral-secondary text-sm font-semibold">
|
||||
Date Joined:
|
||||
</p>
|
||||
<Skeleton className="h-5 w-36 rounded-lg" />
|
||||
</div>
|
||||
{/* Tenant ID */}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-text-neutral-secondary text-sm font-semibold">
|
||||
Tenant ID:
|
||||
</p>
|
||||
<Skeleton className="h-5 w-32 rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Roles */}
|
||||
<Card>
|
||||
<CardContent className="p-3">
|
||||
<h4 className="mb-3 text-sm font-semibold">Roles</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Skeleton key={i} className="h-6 w-20 rounded-full" />
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Memberships */}
|
||||
<Card>
|
||||
<CardContent className="p-3">
|
||||
<h4 className="mb-3 text-sm font-semibold">Memberships</h4>
|
||||
<div className="flex flex-col gap-2">
|
||||
{[1, 2].map((i) => (
|
||||
<Skeleton key={i} className="h-16 w-full rounded-md" />
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
<UserBasicInfoSkeleton />
|
||||
<div className="flex flex-col gap-6 xl:flex-row">
|
||||
<div className="flex w-full flex-col gap-6 xl:max-w-[50%]">
|
||||
<RolesCardSkeleton />
|
||||
</div>
|
||||
<div className="flex w-full flex-col gap-6 xl:max-w-[50%]">
|
||||
<OrganizationsCardSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const UserBasicInfoSkeleton = () => {
|
||||
return (
|
||||
<Card variant="base" padding="none" className="p-4">
|
||||
<CardContent>
|
||||
{/* Avatar + name / email */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Skeleton className="size-10 rounded-full" />
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Skeleton className="h-4 w-32 rounded" />
|
||||
<Skeleton className="h-3 w-48 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
{/* Date Joined + Organization ID */}
|
||||
<div className="flex flex-row gap-4 md:gap-8">
|
||||
<FieldSkeleton labelWidth="w-20" valueWidth="w-24" />
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-1.5">
|
||||
<Skeleton className="h-3 w-28 rounded" />
|
||||
<Skeleton className="h-8 w-full max-w-xs rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const RolesCardSkeleton = () => {
|
||||
return (
|
||||
<Card variant="base" padding="none" className="p-4">
|
||||
<CardContent>
|
||||
{/* Header: "Active roles" + subtitle */}
|
||||
<div className="mb-6 flex flex-col gap-1.5">
|
||||
<Skeleton className="h-6 w-28 rounded" />
|
||||
<Skeleton className="h-3 w-48 rounded" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<RoleItemSkeleton />
|
||||
<RoleItemSkeleton />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// Mirrors <RoleItem>: inner card with a role badge + state on the left, a
|
||||
// details toggle on the right, and a grid of permission rows below.
|
||||
const RoleItemSkeleton = () => {
|
||||
return (
|
||||
<Card variant="inner">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="h-5 w-20 rounded-full" />
|
||||
<Skeleton className="h-3 w-16 rounded" />
|
||||
</div>
|
||||
<Skeleton className="h-5 w-20 rounded" />
|
||||
</div>
|
||||
<div className="border-border-neutral-primary mt-4 grid grid-cols-1 gap-3 border-t pt-4 md:grid-cols-2">
|
||||
{[0, 1, 2, 3].map((i) => (
|
||||
<div key={i} className="flex items-center gap-2">
|
||||
<Skeleton className="size-4 rounded-full" />
|
||||
<Skeleton className="h-3 w-24 rounded" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const OrganizationsCardSkeleton = () => {
|
||||
return (
|
||||
<Card variant="base" padding="none" className="p-4">
|
||||
<CardHeader>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Skeleton className="h-5 w-32 rounded" />
|
||||
<Skeleton className="h-3 w-52 rounded" />
|
||||
</div>
|
||||
<CardAction>
|
||||
<Skeleton className="h-8 w-36 rounded-md" />
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col gap-2">
|
||||
<MembershipItemSkeleton />
|
||||
<MembershipItemSkeleton />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// Mirrors <MembershipItem>: role badge (left), Name / Joined fields, and the
|
||||
// "Active" badge (right).
|
||||
const MembershipItemSkeleton = () => {
|
||||
return (
|
||||
<Card variant="inner" className="p-2">
|
||||
<div className="flex w-full flex-col gap-2 sm:flex-row sm:items-center sm:gap-4">
|
||||
<Skeleton className="h-5 w-16 rounded-full" />
|
||||
<div className="flex flex-row flex-wrap gap-1 gap-x-4">
|
||||
<FieldSkeleton inline labelWidth="w-10" valueWidth="w-24" />
|
||||
<FieldSkeleton inline labelWidth="w-14" valueWidth="w-20" />
|
||||
</div>
|
||||
<div className="ml-auto">
|
||||
<Skeleton className="h-5 w-14 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
// Label + value pair. `inline` lays them side by side (info fields inside a
|
||||
// row); otherwise stacked (a standalone field).
|
||||
const FieldSkeleton = ({
|
||||
labelWidth,
|
||||
valueWidth,
|
||||
inline = false,
|
||||
}: {
|
||||
labelWidth: string;
|
||||
valueWidth: string;
|
||||
inline?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div className={inline ? "flex items-center gap-2" : "flex flex-col gap-1.5"}>
|
||||
<Skeleton className={`h-3 ${labelWidth} rounded`} />
|
||||
<Skeleton className={`h-4 ${valueWidth} rounded`} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,37 +1,102 @@
|
||||
import React from "react";
|
||||
|
||||
import { Card } from "@/components/shadcn/card/card";
|
||||
import { Skeleton } from "@/components/shadcn/skeleton/skeleton";
|
||||
|
||||
export const SkeletonTableUser = () => {
|
||||
const SkeletonTableRow = () => {
|
||||
return (
|
||||
<Card variant="base" padding="md" className="flex flex-col gap-4">
|
||||
{/* Table headers */}
|
||||
<div className="hidden gap-4 md:flex">
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-2/12" />
|
||||
<Skeleton className="h-8 w-1/12" />
|
||||
</div>
|
||||
|
||||
{/* Table body */}
|
||||
<div className="flex flex-col gap-3">
|
||||
{[...Array(10)].map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-col gap-4 md:flex-row md:items-center"
|
||||
>
|
||||
<Skeleton className="h-12 w-full md:w-2/12" />
|
||||
<Skeleton className="h-12 w-full md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-2/12" />
|
||||
<Skeleton className="hidden h-12 md:block md:w-1/12" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<tr className="border-border-neutral-secondary border-b last:border-b-0">
|
||||
{/* Name */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-28 rounded" />
|
||||
</td>
|
||||
{/* Email */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-32 rounded" />
|
||||
</td>
|
||||
{/* Role */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-20 rounded" />
|
||||
</td>
|
||||
{/* Company name */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
</td>
|
||||
{/* Joined - date */}
|
||||
<td className="px-3 py-4">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
</td>
|
||||
{/* Actions */}
|
||||
<td className="px-2 py-4">
|
||||
<Skeleton className="size-6 rounded" />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export const SkeletonTableUser = () => {
|
||||
const rows = 10;
|
||||
|
||||
return (
|
||||
<div className="border-border-neutral-secondary bg-bg-neutral-secondary flex w-full flex-col gap-4 overflow-hidden rounded-[14px] border p-4 shadow-sm">
|
||||
{/* Toolbar: Search + Total entries */}
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Search icon button */}
|
||||
<Skeleton className="size-10 rounded-md" />
|
||||
{/* Total entries */}
|
||||
<Skeleton className="h-4 w-28 rounded" />
|
||||
</div>
|
||||
|
||||
{/* Table */}
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-border-neutral-secondary border-b">
|
||||
{/* Name */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-12 rounded" />
|
||||
</th>
|
||||
{/* Email */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-14 rounded" />
|
||||
</th>
|
||||
{/* Role */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-10 rounded" />
|
||||
</th>
|
||||
{/* Company name */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-28 rounded" />
|
||||
</th>
|
||||
{/* Joined */}
|
||||
<th className="px-3 py-3 text-left">
|
||||
<Skeleton className="h-4 w-16 rounded" />
|
||||
</th>
|
||||
{/* Actions - empty header */}
|
||||
<th className="w-10 py-3" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Array.from({ length: rows }).map((_, i) => (
|
||||
<SkeletonTableRow key={i} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* Pagination */}
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
{/* Rows per page */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
<Skeleton className="h-9 w-16 rounded-md" />
|
||||
</div>
|
||||
{/* Page info + navigation */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Skeleton className="h-4 w-24 rounded" />
|
||||
<div className="flex gap-1">
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
<Skeleton className="size-9 rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user