@stevederico/skateboard-ui 2.14.0 → 2.16.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/.claude/settings.local.json +16 -0
- package/.playwright-mcp/dark-mode-borders.png +0 -0
- package/.playwright-mcp/dark-mode-inputs.png +0 -0
- package/.playwright-mcp/dark-sidebar.png +0 -0
- package/.playwright-mcp/features-centered-v2.png +0 -0
- package/.playwright-mcp/features-centered-v3.png +0 -0
- package/.playwright-mcp/features-centered.png +0 -0
- package/.playwright-mcp/features-check.png +0 -0
- package/.playwright-mcp/landing-dark.png +0 -0
- package/.playwright-mcp/light-mode-borders.png +0 -0
- package/.playwright-mcp/sidebar-dark-mode.png +0 -0
- package/.playwright-mcp/sidebar-light-mode.png +0 -0
- package/.playwright-mcp/signin-dark.png +0 -0
- package/CHANGELOG.md +5 -0
- package/core/DynamicIcon.jsx +12 -58
- package/core/Utilities.js +21 -1
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(for file in App.jsx AppSidebar.jsx Context.jsx Layout.jsx TabBar.jsx TextView.jsx SettingsView.jsx SignInView.jsx LandingView.jsx UpgradeSheet.jsx PaymentView.jsx SignUpView.jsx)",
|
|
5
|
+
"Bash(do)",
|
|
6
|
+
"Bash(if [ -f \"$file\" ])",
|
|
7
|
+
"Bash(then)",
|
|
8
|
+
"Bash(node --check:*)",
|
|
9
|
+
"Bash(echo:*)",
|
|
10
|
+
"Bash(fi)",
|
|
11
|
+
"Bash(done)",
|
|
12
|
+
"Bash(git commit:*)",
|
|
13
|
+
"Bash(git push:*)"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/CHANGELOG.md
CHANGED
package/core/DynamicIcon.jsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { cn } from "../shadcn/lib/utils.js";
|
|
1
|
+
import * as LucideIcons from "lucide-react";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Convert a kebab-case, snake_case, or space-separated string to PascalCase.
|
|
@@ -27,42 +26,6 @@ function toKebabCase(str) {
|
|
|
27
26
|
.toLowerCase();
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
/** Cache of resolved icon components keyed by name */
|
|
31
|
-
const iconCache = new Map();
|
|
32
|
-
|
|
33
|
-
/** Cache of in-flight import promises keyed by module name */
|
|
34
|
-
const importCache = new Map();
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Load a single Lucide icon by its kebab-case file name (e.g. "arrow-right").
|
|
38
|
-
* Each icon is imported individually (~1KB) instead of loading the entire
|
|
39
|
-
* icon library. Results are cached for instant subsequent lookups.
|
|
40
|
-
*
|
|
41
|
-
* @param {string} kebabName - kebab-case icon file name
|
|
42
|
-
* @param {string} cacheKey - PascalCase name used as cache key
|
|
43
|
-
* @returns {Promise<React.ComponentType|null>} Icon component or null
|
|
44
|
-
*/
|
|
45
|
-
function loadIcon(kebabName, cacheKey) {
|
|
46
|
-
if (iconCache.has(cacheKey)) return Promise.resolve(iconCache.get(cacheKey));
|
|
47
|
-
if (importCache.has(cacheKey)) return importCache.get(cacheKey);
|
|
48
|
-
|
|
49
|
-
const promise = import(`/node_modules/lucide-react/dist/esm/icons/${kebabName}.js`)
|
|
50
|
-
.then((mod) => {
|
|
51
|
-
const Icon = mod.default || null;
|
|
52
|
-
iconCache.set(cacheKey, Icon);
|
|
53
|
-
importCache.delete(cacheKey);
|
|
54
|
-
return Icon;
|
|
55
|
-
})
|
|
56
|
-
.catch(() => {
|
|
57
|
-
iconCache.set(cacheKey, null);
|
|
58
|
-
importCache.delete(cacheKey);
|
|
59
|
-
return null;
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
importCache.set(cacheKey, promise);
|
|
63
|
-
return promise;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
29
|
/**
|
|
67
30
|
* Resolve an icon name in any format to a PascalCase name and kebab-case file path.
|
|
68
31
|
* e.g. "layout-dashboard" → { pascal: "LayoutDashboard", kebab: "layout-dashboard" }
|
|
@@ -84,23 +47,22 @@ function toIconName(name) {
|
|
|
84
47
|
}
|
|
85
48
|
|
|
86
49
|
/**
|
|
87
|
-
* Check if a name string
|
|
88
|
-
* Returns false for unloaded or invalid icons.
|
|
50
|
+
* Check if a name string can be resolved to a valid Lucide icon.
|
|
89
51
|
*
|
|
90
52
|
* @param {string} name - Icon name to check
|
|
91
|
-
* @returns {boolean} True if icon
|
|
53
|
+
* @returns {boolean} True if icon exists in the Lucide library
|
|
92
54
|
*/
|
|
93
55
|
export function canResolveIcon(name) {
|
|
94
56
|
const { pascal } = toIconName(name);
|
|
95
|
-
return
|
|
57
|
+
return pascal in LucideIcons && typeof LucideIcons[pascal] === "function";
|
|
96
58
|
}
|
|
97
59
|
|
|
98
60
|
/**
|
|
99
|
-
* Render a Lucide icon by name string
|
|
61
|
+
* Render a Lucide icon by name string using a static import of all icons.
|
|
100
62
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
63
|
+
* All icons are bundled at build time from lucide-react, ensuring they work
|
|
64
|
+
* in both Vite dev mode and production builds. Icons are looked up synchronously
|
|
65
|
+
* by PascalCase name from the LucideIcons namespace.
|
|
104
66
|
*
|
|
105
67
|
* Accepts kebab-case ("layout-dashboard"), PascalCase ("LayoutDashboard"),
|
|
106
68
|
* or legacy prefixed ("IconLayoutDashboard") names.
|
|
@@ -111,7 +73,7 @@ export function canResolveIcon(name) {
|
|
|
111
73
|
* @param {string} [props.color='currentColor'] - Icon stroke color
|
|
112
74
|
* @param {number} [props.strokeWidth=2] - Stroke width
|
|
113
75
|
* @param {string} [props.className] - Additional CSS classes
|
|
114
|
-
* @returns {JSX.Element|null} Rendered icon or null if
|
|
76
|
+
* @returns {JSX.Element|null} Rendered icon or null if not found
|
|
115
77
|
*
|
|
116
78
|
* @example
|
|
117
79
|
* import DynamicIcon from '@stevederico/skateboard-ui/DynamicIcon';
|
|
@@ -128,18 +90,10 @@ const DynamicIcon = ({
|
|
|
128
90
|
className,
|
|
129
91
|
...props
|
|
130
92
|
}) => {
|
|
131
|
-
const { pascal
|
|
132
|
-
|
|
133
|
-
const [Icon, setIcon] = useState(() => iconCache.get(pascal) || null);
|
|
134
|
-
|
|
135
|
-
useEffect(() => {
|
|
136
|
-
if (Icon) return;
|
|
137
|
-
loadIcon(kebab, pascal).then((resolved) => {
|
|
138
|
-
if (resolved) setIcon(() => resolved);
|
|
139
|
-
});
|
|
140
|
-
}, [pascal, kebab, Icon]);
|
|
93
|
+
const { pascal } = toIconName(name);
|
|
94
|
+
const Icon = LucideIcons[pascal];
|
|
141
95
|
|
|
142
|
-
if (!Icon) return null;
|
|
96
|
+
if (!Icon || typeof Icon !== "function") return null;
|
|
143
97
|
|
|
144
98
|
return (
|
|
145
99
|
<Icon
|
package/core/Utilities.js
CHANGED
|
@@ -723,11 +723,31 @@ export async function apiRequest(endpoint, options = {}) {
|
|
|
723
723
|
if (timeoutId) clearTimeout(timeoutId);
|
|
724
724
|
}
|
|
725
725
|
|
|
726
|
-
// Handle 401 (redirect to signout,
|
|
726
|
+
// Handle 401 (redirect to signout, or show auth overlay and retry)
|
|
727
727
|
if (response.status === 401) {
|
|
728
728
|
if (getConstants().authOverlay !== true) {
|
|
729
729
|
window.location.href = '/signout';
|
|
730
|
+
throw new Error('Unauthorized');
|
|
730
731
|
}
|
|
732
|
+
|
|
733
|
+
// In authOverlay mode: show sign-in overlay, retry request after auth
|
|
734
|
+
const dispatch = getDispatch();
|
|
735
|
+
if (dispatch) {
|
|
736
|
+
return new Promise((resolve, reject) => {
|
|
737
|
+
dispatch({
|
|
738
|
+
type: 'SHOW_AUTH_OVERLAY',
|
|
739
|
+
payload: async () => {
|
|
740
|
+
try {
|
|
741
|
+
const result = await apiRequest(endpoint, options);
|
|
742
|
+
resolve(result);
|
|
743
|
+
} catch (err) {
|
|
744
|
+
reject(err);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
|
|
731
751
|
throw new Error('Unauthorized');
|
|
732
752
|
}
|
|
733
753
|
|