@stevederico/skateboard-ui 1.2.15 → 1.2.16
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/README.md +213 -49
- package/SignInView.jsx +3 -3
- package/SignUpView.jsx +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# skateboard-ui
|
|
2
2
|
|
|
3
|
-
React component library
|
|
3
|
+
React component library for rapid application development. Built with TailwindCSS v4 and shadcn/ui.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,58 +8,195 @@ React component library built with TailwindCSS and shadcn/ui for rapid applicati
|
|
|
8
8
|
npm install @stevederico/skateboard-ui
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import './assets/styles.css';
|
|
15
|
+
import { createSkateboardApp } from '@stevederico/skateboard-ui/App';
|
|
16
|
+
import constants from './constants.json';
|
|
17
|
+
import HomeView from './components/HomeView.jsx';
|
|
18
|
+
|
|
19
|
+
const appRoutes = [
|
|
20
|
+
{ path: 'home', element: <HomeView /> }
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
createSkateboardApp({ constants, appRoutes });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That's it! You get routing, auth, layout, landing page, settings, and payments.
|
|
27
|
+
|
|
11
28
|
## Components
|
|
12
29
|
|
|
13
30
|
### Core Components
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
|
|
32
|
+
| Component | Import | Description |
|
|
33
|
+
|-----------|--------|-------------|
|
|
34
|
+
| Header | `@stevederico/skateboard-ui/Header` | App header with title and action button |
|
|
35
|
+
| Layout | `@stevederico/skateboard-ui/Layout` | Page layout with sidebar/tabbar |
|
|
36
|
+
| AppSidebar | `@stevederico/skateboard-ui/AppSidebar` | Desktop navigation sidebar |
|
|
37
|
+
| TabBar | `@stevederico/skateboard-ui/TabBar` | Mobile bottom navigation |
|
|
38
|
+
| DynamicIcon | `@stevederico/skateboard-ui/DynamicIcon` | Lucide icon by name |
|
|
39
|
+
| ThemeToggle | `@stevederico/skateboard-ui/ThemeToggle` | Dark/light mode switch |
|
|
40
|
+
| Sheet | `@stevederico/skateboard-ui/Sheet` | Slide-out panel |
|
|
41
|
+
| UpgradeSheet | `@stevederico/skateboard-ui/UpgradeSheet` | Premium upgrade UI |
|
|
42
|
+
| ErrorBoundary | `@stevederico/skateboard-ui/ErrorBoundary` | Error boundary wrapper |
|
|
22
43
|
|
|
23
44
|
### View Components
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
|
|
46
|
+
| Component | Import | Description |
|
|
47
|
+
|-----------|--------|-------------|
|
|
48
|
+
| LandingView | `@stevederico/skateboard-ui/LandingView` | Landing page |
|
|
49
|
+
| SignInView | `@stevederico/skateboard-ui/SignInView` | Sign in form |
|
|
50
|
+
| SignUpView | `@stevederico/skateboard-ui/SignUpView` | Sign up form |
|
|
51
|
+
| SignOutView | `@stevederico/skateboard-ui/SignOutView` | Sign out handler |
|
|
52
|
+
| SettingsView | `@stevederico/skateboard-ui/SettingsView` | User settings |
|
|
53
|
+
| PaymentView | `@stevederico/skateboard-ui/PaymentView` | Stripe payment |
|
|
54
|
+
| TextView | `@stevederico/skateboard-ui/TextView` | Legal pages |
|
|
55
|
+
| NotFound | `@stevederico/skateboard-ui/NotFound` | 404 page |
|
|
34
56
|
|
|
35
57
|
### shadcn/ui Components
|
|
36
|
-
Full set of shadcn/ui primitives available at `@stevederico/skateboard-ui/shadcn/ui/*`
|
|
37
58
|
|
|
38
|
-
|
|
59
|
+
43 components available at `@stevederico/skateboard-ui/shadcn/ui/*`:
|
|
39
60
|
|
|
40
61
|
```javascript
|
|
41
62
|
import { Button } from '@stevederico/skateboard-ui/shadcn/ui/button'
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
63
|
+
import { Card } from '@stevederico/skateboard-ui/shadcn/ui/card'
|
|
64
|
+
import { Input } from '@stevederico/skateboard-ui/shadcn/ui/input'
|
|
65
|
+
import { Dialog } from '@stevederico/skateboard-ui/shadcn/ui/dialog'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Usage Examples
|
|
69
|
+
|
|
70
|
+
### Header
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
import Header from '@stevederico/skateboard-ui/Header';
|
|
74
|
+
|
|
75
|
+
<Header
|
|
76
|
+
title="Dashboard"
|
|
77
|
+
buttonTitle="Add"
|
|
78
|
+
onButtonTitleClick={() => console.log('clicked')}
|
|
79
|
+
/>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### DynamicIcon
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
import DynamicIcon from '@stevederico/skateboard-ui/DynamicIcon';
|
|
86
|
+
|
|
87
|
+
<DynamicIcon name="home" size={24} />
|
|
88
|
+
<DynamicIcon name="settings" size={20} className="text-muted" />
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Icons from [lucide-react](https://lucide.dev/icons/).
|
|
92
|
+
|
|
93
|
+
### UpgradeSheet
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
import { useRef } from 'react';
|
|
97
|
+
import UpgradeSheet from '@stevederico/skateboard-ui/UpgradeSheet';
|
|
98
|
+
import { showUpgradeSheet } from '@stevederico/skateboard-ui/Utilities';
|
|
99
|
+
|
|
100
|
+
function MyComponent() {
|
|
101
|
+
const upgradeRef = useRef();
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<>
|
|
105
|
+
<button onClick={() => showUpgradeSheet(upgradeRef)}>
|
|
106
|
+
Upgrade
|
|
107
|
+
</button>
|
|
108
|
+
<UpgradeSheet ref={upgradeRef} userEmail={user.email} />
|
|
109
|
+
</>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Context
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
import { getState } from '@stevederico/skateboard-ui/Context';
|
|
118
|
+
|
|
119
|
+
function MyComponent() {
|
|
120
|
+
const { state, dispatch } = getState();
|
|
121
|
+
|
|
122
|
+
// Access user
|
|
123
|
+
const user = state.user;
|
|
124
|
+
|
|
125
|
+
// Update user
|
|
126
|
+
dispatch({ type: 'SET_USER', payload: newUser });
|
|
127
|
+
|
|
128
|
+
// Sign out
|
|
129
|
+
dispatch({ type: 'CLEAR_USER' });
|
|
130
|
+
}
|
|
44
131
|
```
|
|
45
132
|
|
|
46
133
|
## Utilities
|
|
47
134
|
|
|
135
|
+
### API Requests
|
|
136
|
+
|
|
48
137
|
```javascript
|
|
49
|
-
import {
|
|
50
|
-
|
|
51
|
-
|
|
138
|
+
import { apiRequest } from '@stevederico/skateboard-ui/Utilities';
|
|
139
|
+
|
|
140
|
+
// GET
|
|
141
|
+
const data = await apiRequest('/deals');
|
|
142
|
+
|
|
143
|
+
// POST
|
|
144
|
+
const newDeal = await apiRequest('/deals', {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
body: JSON.stringify({ name: 'New Deal', amount: 5000 })
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Features:
|
|
151
|
+
- Auto-includes credentials
|
|
152
|
+
- Auto-adds CSRF token for mutations
|
|
153
|
+
- Auto-redirects to /signout on 401
|
|
154
|
+
|
|
155
|
+
### Data Fetching Hook
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
import { useListData } from '@stevederico/skateboard-ui/Utilities';
|
|
159
|
+
|
|
160
|
+
function DealsList() {
|
|
161
|
+
const { data, loading, error, refetch } = useListData('/deals');
|
|
162
|
+
|
|
163
|
+
if (loading) return <div>Loading...</div>;
|
|
164
|
+
if (error) return <div>Error: {error}</div>;
|
|
165
|
+
|
|
166
|
+
return data.map(deal => <DealCard key={deal.id} {...deal} />);
|
|
167
|
+
}
|
|
52
168
|
```
|
|
53
169
|
|
|
54
|
-
|
|
170
|
+
### Usage Tracking
|
|
55
171
|
|
|
56
172
|
```javascript
|
|
57
|
-
import
|
|
173
|
+
import { getRemainingUsage, trackUsage, showUpgradeSheet } from '@stevederico/skateboard-ui/Utilities';
|
|
174
|
+
|
|
175
|
+
// Check remaining usage
|
|
176
|
+
const usage = await getRemainingUsage('messages');
|
|
177
|
+
// { remaining: 15, total: 20, isSubscriber: false }
|
|
178
|
+
|
|
179
|
+
// Track usage (decrements remaining)
|
|
180
|
+
const updated = await trackUsage('messages');
|
|
181
|
+
|
|
182
|
+
// Show upgrade prompt
|
|
183
|
+
showUpgradeSheet(upgradeSheetRef);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Auth Utilities
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
import { isAuthenticated, getCurrentUser } from '@stevederico/skateboard-ui/Utilities';
|
|
190
|
+
|
|
191
|
+
if (isAuthenticated()) {
|
|
192
|
+
const user = getCurrentUser();
|
|
193
|
+
}
|
|
58
194
|
```
|
|
59
195
|
|
|
60
196
|
## UI Visibility Control
|
|
61
197
|
|
|
62
|
-
### Static
|
|
198
|
+
### Static (constants.json)
|
|
199
|
+
|
|
63
200
|
```json
|
|
64
201
|
{
|
|
65
202
|
"hideSidebar": true,
|
|
@@ -67,36 +204,63 @@ import ProtectedRoute from '@stevederico/skateboard-ui/ProtectedRoute'
|
|
|
67
204
|
}
|
|
68
205
|
```
|
|
69
206
|
|
|
70
|
-
### Programmatic
|
|
207
|
+
### Programmatic
|
|
208
|
+
|
|
71
209
|
```javascript
|
|
72
|
-
import {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} from '@stevederico/skateboard-ui/Utilities'
|
|
79
|
-
|
|
80
|
-
// Individual controls
|
|
81
|
-
hideSidebar()
|
|
82
|
-
showSidebar()
|
|
83
|
-
hideTabBar()
|
|
84
|
-
showTabBar()
|
|
210
|
+
import { showSidebar, hideSidebar, showTabBar, hideTabBar, setUIVisibility } from '@stevederico/skateboard-ui/Utilities';
|
|
211
|
+
|
|
212
|
+
hideSidebar();
|
|
213
|
+
showSidebar();
|
|
214
|
+
hideTabBar();
|
|
215
|
+
showTabBar();
|
|
85
216
|
|
|
86
217
|
// Batch control
|
|
87
|
-
setUIVisibility({ sidebar: false, tabBar: false })
|
|
218
|
+
setUIVisibility({ sidebar: false, tabBar: false });
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Styling
|
|
222
|
+
|
|
223
|
+
Import base theme and override as needed:
|
|
224
|
+
|
|
225
|
+
```css
|
|
226
|
+
/* styles.css */
|
|
227
|
+
@import "@stevederico/skateboard-ui/styles.css";
|
|
228
|
+
|
|
229
|
+
@source '../../node_modules/@stevederico/skateboard-ui';
|
|
230
|
+
|
|
231
|
+
@theme {
|
|
232
|
+
--color-app: var(--color-purple-500);
|
|
233
|
+
}
|
|
88
234
|
```
|
|
89
235
|
|
|
236
|
+
### Theme Variables
|
|
237
|
+
|
|
238
|
+
| Variable | Description |
|
|
239
|
+
|----------|-------------|
|
|
240
|
+
| `--color-app` | Primary brand color |
|
|
241
|
+
| `--background` | Page background |
|
|
242
|
+
| `--foreground` | Text color |
|
|
243
|
+
| `--accent` | Secondary backgrounds |
|
|
244
|
+
| `--radius` | Border radius |
|
|
245
|
+
|
|
246
|
+
Dark mode is automatic via CSS custom properties.
|
|
247
|
+
|
|
248
|
+
## Protected Routes
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
import ProtectedRoute from '@stevederico/skateboard-ui/ProtectedRoute';
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Used internally by createSkateboardApp. Redirects to /signin if not authenticated.
|
|
255
|
+
|
|
90
256
|
## Dependencies
|
|
91
257
|
|
|
92
|
-
Built on:
|
|
93
258
|
- React 19.1+
|
|
259
|
+
- react-router-dom 7.0+
|
|
94
260
|
- Radix UI primitives
|
|
95
|
-
- TailwindCSS
|
|
96
|
-
- lucide-react
|
|
97
|
-
- class-variance-authority
|
|
261
|
+
- TailwindCSS 4.0+
|
|
262
|
+
- lucide-react
|
|
98
263
|
|
|
99
264
|
## Repository
|
|
100
265
|
|
|
101
266
|
https://github.com/stevederico/skateboard-ui
|
|
102
|
-
|
package/SignInView.jsx
CHANGED
|
@@ -52,10 +52,10 @@ export default function LoginForm({
|
|
|
52
52
|
|
|
53
53
|
if (response.ok) {
|
|
54
54
|
const data = await response.json();
|
|
55
|
-
// Save CSRF token
|
|
55
|
+
// Save CSRF token to localStorage for isAuthenticated() check
|
|
56
56
|
const csrfCookie = document.cookie.split('; ').find(row => row.startsWith('csrf_token='));
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const csrfToken = csrfCookie ? csrfCookie.split('=')[1] : data.csrfToken;
|
|
58
|
+
if (csrfToken) {
|
|
59
59
|
const appName = constants.appName || 'skateboard';
|
|
60
60
|
const csrfKey = `${appName.toLowerCase().replace(/\s+/g, '-')}_csrf`;
|
|
61
61
|
localStorage.setItem(csrfKey, csrfToken);
|
package/SignUpView.jsx
CHANGED
|
@@ -55,10 +55,10 @@ export default function LoginForm({
|
|
|
55
55
|
|
|
56
56
|
if (response.ok) {
|
|
57
57
|
const data = await response.json();
|
|
58
|
-
// Save CSRF token
|
|
58
|
+
// Save CSRF token to localStorage for isAuthenticated() check
|
|
59
59
|
const csrfCookie = document.cookie.split('; ').find(row => row.startsWith('csrf_token='));
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const csrfToken = csrfCookie ? csrfCookie.split('=')[1] : data.csrfToken;
|
|
61
|
+
if (csrfToken) {
|
|
62
62
|
const appName = constants.appName || 'skateboard';
|
|
63
63
|
const csrfKey = `${appName.toLowerCase().replace(/\s+/g, '-')}_csrf`;
|
|
64
64
|
localStorage.setItem(csrfKey, csrfToken);
|