@stevederico/skateboard-ui 4.12.1 → 4.13.0
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.
- package/CHANGELOG.md +5 -0
- package/README.md +24 -0
- package/dist/components/ProtectedRoute.d.ts +3 -0
- package/dist/components/ProtectedRoute.js +5 -2
- package/dist/ui/skeleton.d.ts +17 -1
- package/dist/ui/skeleton.js +22 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -124,6 +124,30 @@ Sets up routing, auth, theming, state, and error handling.
|
|
|
124
124
|
| `/terms`, `/privacy`, `/eula`, `/subscription` | TextView | No |
|
|
125
125
|
| `*` | NotFound | No |
|
|
126
126
|
|
|
127
|
+
## Loading skeletons
|
|
128
|
+
|
|
129
|
+
Prefer **skeletons** for content waits (lists, page data, auth gate inside layout). Keep **spinners** for actions (submit, redirect).
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
import {
|
|
133
|
+
Skeleton,
|
|
134
|
+
PageSkeleton,
|
|
135
|
+
CardListSkeleton,
|
|
136
|
+
SettingsSkeleton,
|
|
137
|
+
} from '@stevederico/skateboard-ui/ui/skeleton';
|
|
138
|
+
|
|
139
|
+
// Generic main-content placeholder (also used by ProtectedRoute while validating /me)
|
|
140
|
+
if (loading) return <PageSkeleton />;
|
|
141
|
+
|
|
142
|
+
// List/grid of avatar+text cards
|
|
143
|
+
if (loading) return <CardListSkeleton count={9} />;
|
|
144
|
+
|
|
145
|
+
// Settings-shaped stacked cards
|
|
146
|
+
if (loading) return <SettingsSkeleton />;
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`ProtectedRoute` renders `PageSkeleton` inside the layout main area while checking the session — sidebar and tab bar stay put.
|
|
150
|
+
|
|
127
151
|
## Authentication
|
|
128
152
|
|
|
129
153
|
Hybrid cookie + localStorage auth:
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* Bypassed for lazy auth (the default) unless constants.authOverlay is false.
|
|
7
7
|
* Bypassed when constants.noLogin is true (no auth required).
|
|
8
8
|
*
|
|
9
|
+
* While checking, renders PageSkeleton inside the layout main content
|
|
10
|
+
* (ProtectedRoute is already nested under Layout / SidebarInset).
|
|
11
|
+
*
|
|
9
12
|
* @returns {JSX.Element} Outlet if authenticated, Navigate to /signin otherwise
|
|
10
13
|
*
|
|
11
14
|
* @example
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import { Navigate, Outlet } from 'react-router';
|
|
4
4
|
import { isAuthenticated, apiRequest, getAppKey, getConstants, isAuthOverlayEnabled } from './core/Utilities.js';
|
|
5
|
-
import {
|
|
5
|
+
import { PageSkeleton } from '../ui/skeleton.js';
|
|
6
6
|
/**
|
|
7
7
|
* Route guard that validates authentication before rendering child routes.
|
|
8
8
|
*
|
|
@@ -11,6 +11,9 @@ import { Spinner } from '../shadcn/ui/spinner.js';
|
|
|
11
11
|
* Bypassed for lazy auth (the default) unless constants.authOverlay is false.
|
|
12
12
|
* Bypassed when constants.noLogin is true (no auth required).
|
|
13
13
|
*
|
|
14
|
+
* While checking, renders PageSkeleton inside the layout main content
|
|
15
|
+
* (ProtectedRoute is already nested under Layout / SidebarInset).
|
|
16
|
+
*
|
|
14
17
|
* @returns {JSX.Element} Outlet if authenticated, Navigate to /signin otherwise
|
|
15
18
|
*
|
|
16
19
|
* @example
|
|
@@ -49,7 +52,7 @@ const ProtectedRoute = () => {
|
|
|
49
52
|
});
|
|
50
53
|
}, []);
|
|
51
54
|
if (status === 'checking') {
|
|
52
|
-
return
|
|
55
|
+
return _jsx(PageSkeleton, {});
|
|
53
56
|
}
|
|
54
57
|
return status === 'valid' ? _jsx(Outlet, {}) : _jsx(Navigate, { to: "/signin", replace: true });
|
|
55
58
|
};
|
package/dist/ui/skeleton.d.ts
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
declare function Skeleton({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Generic main-content loading placeholder (header bar + text + card grid).
|
|
5
|
+
* Use inside SidebarInset / route content — not as a full-app takeover.
|
|
6
|
+
*/
|
|
7
|
+
declare function PageSkeleton({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
8
|
+
/**
|
|
9
|
+
* List/grid of bordered cards with avatar + text lines.
|
|
10
|
+
* Match count to typical list size so layout stays stable.
|
|
11
|
+
*/
|
|
12
|
+
declare function CardListSkeleton({ count, className, ...props }: React.ComponentProps<"div"> & {
|
|
13
|
+
count?: number;
|
|
14
|
+
}): React.JSX.Element;
|
|
15
|
+
/**
|
|
16
|
+
* Settings page placeholder — header strip + stacked max-w-lg cards.
|
|
17
|
+
*/
|
|
18
|
+
declare function SettingsSkeleton({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
19
|
+
export { Skeleton, PageSkeleton, CardListSkeleton, SettingsSkeleton };
|
package/dist/ui/skeleton.js
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cn } from "../shadcn/lib/utils.js";
|
|
4
4
|
function Skeleton({ className, ...props }) {
|
|
5
5
|
return (_jsx("div", { "data-slot": "skeleton", className: cn("animate-pulse rounded-md bg-muted", className), ...props }));
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Generic main-content loading placeholder (header bar + text + card grid).
|
|
9
|
+
* Use inside SidebarInset / route content — not as a full-app takeover.
|
|
10
|
+
*/
|
|
11
|
+
function PageSkeleton({ className, ...props }) {
|
|
12
|
+
return (_jsxs("div", { "data-slot": "page-skeleton", className: cn("flex flex-1 flex-col gap-4 p-4 lg:p-6", className), "aria-busy": "true", "aria-live": "polite", ...props, children: [_jsx(Skeleton, { className: "h-8 w-48" }), _jsx(Skeleton, { className: "h-4 w-full max-w-xl" }), _jsx(Skeleton, { className: "h-4 w-4/5 max-w-lg" }), _jsx("div", { className: "grid grid-cols-1 gap-4 pt-4 md:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsx(Skeleton, { className: "h-32 w-full" }, i))) })] }));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* List/grid of bordered cards with avatar + text lines.
|
|
16
|
+
* Match count to typical list size so layout stays stable.
|
|
17
|
+
*/
|
|
18
|
+
function CardListSkeleton({ count = 6, className, ...props }) {
|
|
19
|
+
return (_jsx("div", { "data-slot": "card-list-skeleton", className: cn("flex flex-1 flex-col gap-4 p-4 lg:p-6", className), "aria-busy": "true", "aria-live": "polite", ...props, children: _jsx("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: count }).map((_, i) => (_jsxs("div", { className: "flex items-start gap-3 rounded-lg border border-border p-4", children: [_jsx(Skeleton, { className: "size-12 shrink-0 rounded-full" }), _jsxs("div", { className: "flex flex-1 flex-col gap-2", children: [_jsx(Skeleton, { className: "h-4 w-2/3" }), _jsx(Skeleton, { className: "h-3 w-1/3" }), _jsx(Skeleton, { className: "mt-1 h-3 w-full" }), _jsx(Skeleton, { className: "h-3 w-4/5" })] })] }, i))) }) }));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Settings page placeholder — header strip + stacked max-w-lg cards.
|
|
23
|
+
*/
|
|
24
|
+
function SettingsSkeleton({ className, ...props }) {
|
|
25
|
+
return (_jsxs("div", { "data-slot": "settings-skeleton", className: cn("flex-1", className), "aria-busy": "true", "aria-live": "polite", ...props, children: [_jsx("div", { className: "flex h-(--header-height) shrink-0 items-center px-4 lg:px-6", children: _jsx(Skeleton, { className: "h-6 w-24" }) }), _jsx("div", { className: "h-px bg-border" }), _jsx("div", { className: "flex flex-col items-center gap-4 p-4", children: Array.from({ length: 3 }).map((_, i) => (_jsxs("div", { className: "flex w-full max-w-lg items-center justify-between gap-4 rounded-xl border border-border p-6", children: [_jsxs("div", { className: "flex flex-1 flex-col gap-2", children: [_jsx(Skeleton, { className: "h-5 w-28" }), _jsx(Skeleton, { className: "h-4 w-48" })] }), _jsx(Skeleton, { className: "h-8 w-20 shrink-0" })] }, i))) })] }));
|
|
26
|
+
}
|
|
27
|
+
export { Skeleton, PageSkeleton, CardListSkeleton, SettingsSkeleton };
|