@stevederico/skateboard-ui 4.12.0 → 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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
+ 4.13.0
4
+
5
+ Add content skeletons
6
+ Skeleton in ProtectedRoute
7
+
8
+ 4.12.1
9
+
10
+ Republish built dist
11
+
3
12
  4.12.0
4
13
 
5
14
  Fix drawer dark-mode text
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:
package/dist/App.js CHANGED
@@ -22,7 +22,7 @@ import AuthOverlay from './components/AuthOverlay.js';
22
22
  * Redirect component for auth routes when authOverlay mode is enabled.
23
23
  *
24
24
  * Dispatches SHOW_AUTH_OVERLAY on mount and redirects to the previous page
25
- * (from location state) or /app/home as fallback.
25
+ * (from location state) or /app as fallback (which routes to the app's defaultRoute).
26
26
  */
27
27
  function AuthRedirect() {
28
28
  const { dispatch } = getState();
@@ -34,7 +34,7 @@ function AuthRedirect() {
34
34
  if (!isAuthenticated()) {
35
35
  dispatch({ type: 'SHOW_AUTH_OVERLAY' });
36
36
  }
37
- const returnTo = location.state?.from || '/app/home';
37
+ const returnTo = location.state?.from || '/app';
38
38
  navigate(returnTo, { replace: true });
39
39
  }, [dispatch, navigate, location.state]);
40
40
  return null;
@@ -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 { Spinner } from '../shadcn/ui/spinner.js';
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 (_jsx("div", { className: "flex items-center justify-center min-h-screen bg-background", children: _jsx(Spinner, { className: "size-6" }) }));
55
+ return _jsx(PageSkeleton, {});
53
56
  }
54
57
  return status === 'valid' ? _jsx(Outlet, {}) : _jsx(Navigate, { to: "/signin", replace: true });
55
58
  };
@@ -6,7 +6,7 @@ import { getCurrentUser, getAppKey } from '../core/Utilities.js';
6
6
  import { Spinner } from '../../shadcn/ui/spinner.js';
7
7
  // Whitelist of allowed redirect paths to prevent open redirect vulnerabilities
8
8
  const ALLOWED_REDIRECT_PREFIXES = ['/app/', '/'];
9
- const DEFAULT_REDIRECT = '/app/home';
9
+ const DEFAULT_REDIRECT = '/app';
10
10
  function isAllowedRedirect(path) {
11
11
  try {
12
12
  // Resolve relative to current origin; reject anything that escapes it.
package/dist/ui/drawer.js CHANGED
@@ -229,7 +229,7 @@ function DrawerContent({ className, children, onPointerDown, onPointerMove, onPo
229
229
  // browser claiming it for native scroll (which would fire pointercancel).
230
230
  // top-auto/m-0/w-full/max-w-none clear the native <dialog> UA inset and
231
231
  // sizing so the drawer pins to the bottom edge at full width.
232
- "fixed inset-x-0 top-auto bottom-0 z-50 m-0 flex max-h-[80vh] w-full max-w-none touch-none flex-col rounded-t-xl border-t border-border bg-background p-0 text-sm outline-none backdrop:bg-black/10 backdrop:transition-opacity backdrop:duration-250 supports-backdrop-filter:backdrop:backdrop-blur-xs", open
232
+ "fixed inset-x-0 top-auto bottom-0 z-50 m-0 flex max-h-[80vh] w-full max-w-none touch-none flex-col rounded-t-xl border-t border-border bg-background p-0 text-sm text-foreground outline-none backdrop:bg-black/10 backdrop:transition-opacity backdrop:duration-250 supports-backdrop-filter:backdrop:backdrop-blur-xs", open
233
233
  ? "animate-in fade-in-0 slide-in-from-bottom-52 duration-300"
234
234
  : dismissingByDrag.current
235
235
  ? ""
@@ -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
- export { Skeleton };
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 };
@@ -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
- export { Skeleton };
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 };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stevederico/skateboard-ui",
3
3
  "private": false,
4
- "version": "4.12.0",
4
+ "version": "4.13.0",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {