@stevederico/skateboard-ui 2.12.0 → 2.14.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 +12 -0
- package/core/DynamicIcon.jsx +75 -45
- package/layout/Sidebar.jsx +1 -1
- package/package.json +1 -1
- package/.claude/settings.local.json +0 -16
- 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
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
2.14.0
|
|
4
|
+
|
|
5
|
+
Switch icons to Lucide React
|
|
6
|
+
Update DynamicIcon Lucide loader
|
|
7
|
+
Remove Tabler icon imports
|
|
8
|
+
|
|
9
|
+
2.13.0
|
|
10
|
+
|
|
11
|
+
Fix DynamicIcon per-icon imports
|
|
12
|
+
Remove lucide-react from views
|
|
13
|
+
Switch views to Tabler icons
|
|
14
|
+
|
|
3
15
|
2.12.0
|
|
4
16
|
|
|
5
17
|
Add skip-to-content link
|
package/core/DynamicIcon.jsx
CHANGED
|
@@ -14,64 +14,96 @@ function toPascalCase(str) {
|
|
|
14
14
|
.join("");
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Convert a PascalCase or camelCase string to kebab-case.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} str - Input string
|
|
21
|
+
* @returns {string} kebab-case version
|
|
22
|
+
*/
|
|
23
|
+
function toKebabCase(str) {
|
|
24
|
+
return str
|
|
25
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
26
|
+
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
|
|
27
|
+
.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
|
|
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();
|
|
19
35
|
|
|
20
36
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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.
|
|
23
40
|
*
|
|
24
|
-
* @
|
|
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
|
|
25
44
|
*/
|
|
26
|
-
function
|
|
27
|
-
if (
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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;
|
|
32
60
|
});
|
|
33
|
-
|
|
34
|
-
|
|
61
|
+
|
|
62
|
+
importCache.set(cacheKey, promise);
|
|
63
|
+
return promise;
|
|
35
64
|
}
|
|
36
65
|
|
|
37
66
|
/**
|
|
38
|
-
* Resolve icon name to a
|
|
67
|
+
* Resolve an icon name in any format to a PascalCase name and kebab-case file path.
|
|
68
|
+
* e.g. "layout-dashboard" → { pascal: "LayoutDashboard", kebab: "layout-dashboard" }
|
|
69
|
+
* "LayoutDashboard" → { pascal: "LayoutDashboard", kebab: "layout-dashboard" }
|
|
39
70
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* @
|
|
71
|
+
* Strips legacy "Icon" prefix from Tabler-style names for backwards compatibility.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} name - Icon name in any case format
|
|
74
|
+
* @returns {{ pascal: string, kebab: string }} Resolved icon name pair
|
|
43
75
|
*/
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
toPascalCase(name),
|
|
49
|
-
name.charAt(0).toUpperCase() + name.slice(1),
|
|
50
|
-
];
|
|
51
|
-
for (const candidate of candidates) {
|
|
52
|
-
if (mod[candidate]) return mod[candidate];
|
|
76
|
+
function toIconName(name) {
|
|
77
|
+
let stripped = name;
|
|
78
|
+
if (stripped.startsWith("Icon") && stripped.length > 4 && stripped[4] === stripped[4].toUpperCase()) {
|
|
79
|
+
stripped = stripped.slice(4);
|
|
53
80
|
}
|
|
54
|
-
|
|
81
|
+
const pascal = /[-_\s]/.test(stripped) ? toPascalCase(stripped) : stripped;
|
|
82
|
+
const kebab = toKebabCase(pascal);
|
|
83
|
+
return { pascal, kebab };
|
|
55
84
|
}
|
|
56
85
|
|
|
57
86
|
/**
|
|
58
|
-
* Check if a name string
|
|
59
|
-
* Returns false
|
|
87
|
+
* Check if a name string has been resolved to a valid icon.
|
|
88
|
+
* Returns false for unloaded or invalid icons.
|
|
60
89
|
*
|
|
61
90
|
* @param {string} name - Icon name to check
|
|
62
|
-
* @returns {boolean} True if
|
|
91
|
+
* @returns {boolean} True if icon is cached and valid
|
|
63
92
|
*/
|
|
64
93
|
export function canResolveIcon(name) {
|
|
65
|
-
|
|
66
|
-
return
|
|
94
|
+
const { pascal } = toIconName(name);
|
|
95
|
+
return iconCache.has(pascal) && iconCache.get(pascal) !== null;
|
|
67
96
|
}
|
|
68
97
|
|
|
69
98
|
/**
|
|
70
|
-
* Render a Lucide icon by name string with lazy loading.
|
|
99
|
+
* Render a Lucide icon by name string with per-icon lazy loading.
|
|
100
|
+
*
|
|
101
|
+
* Each icon is imported individually from lucide-react (~1KB per icon)
|
|
102
|
+
* instead of loading the entire library. Resolved icons are cached in memory
|
|
103
|
+
* for instant rendering on subsequent uses.
|
|
71
104
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* under 50ms from cache).
|
|
105
|
+
* Accepts kebab-case ("layout-dashboard"), PascalCase ("LayoutDashboard"),
|
|
106
|
+
* or legacy prefixed ("IconLayoutDashboard") names.
|
|
75
107
|
*
|
|
76
108
|
* @param {Object} props
|
|
77
109
|
* @param {string} props.name - Icon name (e.g. "home", "arrow-right", "Settings")
|
|
@@ -96,18 +128,16 @@ const DynamicIcon = ({
|
|
|
96
128
|
className,
|
|
97
129
|
...props
|
|
98
130
|
}) => {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
});
|
|
131
|
+
const { pascal, kebab } = toIconName(name);
|
|
132
|
+
|
|
133
|
+
const [Icon, setIcon] = useState(() => iconCache.get(pascal) || null);
|
|
103
134
|
|
|
104
135
|
useEffect(() => {
|
|
105
136
|
if (Icon) return;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
setIcon(() => resolved);
|
|
137
|
+
loadIcon(kebab, pascal).then((resolved) => {
|
|
138
|
+
if (resolved) setIcon(() => resolved);
|
|
109
139
|
});
|
|
110
|
-
}, [
|
|
140
|
+
}, [pascal, kebab, Icon]);
|
|
111
141
|
|
|
112
142
|
if (!Icon) return null;
|
|
113
143
|
|
|
@@ -116,7 +146,7 @@ const DynamicIcon = ({
|
|
|
116
146
|
size={size}
|
|
117
147
|
color={color}
|
|
118
148
|
strokeWidth={strokeWidth}
|
|
119
|
-
className={
|
|
149
|
+
className={className}
|
|
120
150
|
{...props}
|
|
121
151
|
/>
|
|
122
152
|
);
|
package/layout/Sidebar.jsx
CHANGED
package/package.json
CHANGED
|
@@ -1,16 +0,0 @@
|
|
|
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
|