@stevederico/skateboard-ui 1.2.4 → 1.2.6

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,4 +1,19 @@
1
1
  # CHANGELOG
2
+ 1.2.5
3
+
4
+ Add sidebar visibility control
5
+ Add tabbar visibility control
6
+ Add hideTabBar constant support
7
+ Add programmatic UI functions
8
+ Fix broken Button export
9
+ Fix SignOutView import path
10
+ Add LandingViewSimple export
11
+ Consolidate DynamicIcon usage
12
+ Remove unused FREE_LIMITS
13
+ Fix ViteConfig duplicate import
14
+ Relax peer dependency versions
15
+ Update README documentation
16
+
2
17
  1.2.4
3
18
 
4
19
  Fix react-router cookie import
package/Context.jsx CHANGED
@@ -1,7 +1,14 @@
1
- import React, { createContext, useContext, useReducer } from 'react';
1
+ import React, { createContext, useContext, useReducer, useEffect } from 'react';
2
2
 
3
3
  const context = createContext();
4
4
 
5
+ // Store dispatch reference for programmatic access outside components
6
+ let _dispatch = null;
7
+
8
+ export function getDispatch() {
9
+ return _dispatch;
10
+ }
11
+
5
12
  // Check if localStorage is available
6
13
  function isLocalStorageAvailable() {
7
14
  try {
@@ -69,7 +76,13 @@ export function ContextProvider({ children, constants }) {
69
76
  }
70
77
  };
71
78
 
72
- const initialState = { user: getInitialUser() };
79
+ const initialState = {
80
+ user: getInitialUser(),
81
+ ui: {
82
+ sidebarVisible: true,
83
+ tabBarVisible: true
84
+ }
85
+ };
73
86
 
74
87
  function reducer(state, action) {
75
88
  const storageKey = getStorageKey();
@@ -91,6 +104,15 @@ export function ContextProvider({ children, constants }) {
91
104
  safeLSRemoveItem(storageKey);
92
105
  return { ...state, user: null };
93
106
  }
107
+ case 'SET_SIDEBAR_VISIBLE': {
108
+ return { ...state, ui: { ...state.ui, sidebarVisible: action.payload } };
109
+ }
110
+ case 'SET_TABBAR_VISIBLE': {
111
+ return { ...state, ui: { ...state.ui, tabBarVisible: action.payload } };
112
+ }
113
+ case 'SET_UI_VISIBILITY': {
114
+ return { ...state, ui: { ...state.ui, ...action.payload } };
115
+ }
94
116
  default:
95
117
  return state;
96
118
  }
@@ -98,6 +120,12 @@ export function ContextProvider({ children, constants }) {
98
120
 
99
121
  const [state, dispatch] = useReducer(reducer, initialState);
100
122
 
123
+ // Store dispatch reference for programmatic access
124
+ useEffect(() => {
125
+ _dispatch = dispatch;
126
+ return () => { _dispatch = null; };
127
+ }, [dispatch]);
128
+
101
129
  return (
102
130
  <context.Provider value={{ state, dispatch }}>
103
131
  {children}
@@ -1,14 +1,6 @@
1
1
  import React from 'react';
2
2
  import constants from "@/constants.json";
3
- import * as LucideIcons from "lucide-react";
4
-
5
- // Dynamic Icon Component
6
- const DynamicIcon = ({ name, size = 24, color = 'currentColor', strokeWidth = 2, ...props }) => {
7
- const toPascalCase = (str) => str.split(/[-_\s]/).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join('');
8
- const possibleNames = [name, toPascalCase(name), name.charAt(0).toUpperCase() + name.slice(1)];
9
- const LucideIcon = possibleNames.find(n => LucideIcons[n]) ? LucideIcons[possibleNames.find(n => LucideIcons[n])] : null;
10
- return LucideIcon ? React.createElement(LucideIcon, { size, color, strokeWidth, ...props }) : null;
11
- };
3
+ import DynamicIcon from './DynamicIcon';
12
4
 
13
5
  export default function LandingView() {
14
6
  return (
package/Layout.jsx CHANGED
@@ -4,8 +4,15 @@ import { SidebarProvider, SidebarTrigger } from "./shadcn/ui/sidebar"
4
4
  import AppSidebar from "./AppSidebar"
5
5
  import { useEffect } from 'react';
6
6
  import constants from "@/constants.json";
7
+ import { getState } from './Context.jsx';
7
8
 
8
9
  export default function Layout({ children }) {
10
+ const { state } = getState();
11
+ const { sidebarVisible, tabBarVisible } = state.ui;
12
+
13
+ // Combine constants (static config) with context state (programmatic control)
14
+ const showSidebar = !constants.hideSidebar && sidebarVisible;
15
+ const showTabBar = !constants.hideTabBar && tabBarVisible;
9
16
 
10
17
  useEffect(() => {
11
18
  const root = document.documentElement;
@@ -41,14 +48,14 @@ export default function Layout({ children }) {
41
48
  }, []);
42
49
 
43
50
  return (
44
- <div className="min-h-screen flex flex-col pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]">
51
+ <div className="min-h-screen flex flex-col pt-[env(safe-area-inset-top)] pb-[calc(5rem+env(safe-area-inset-bottom))] md:pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]">
45
52
  <SidebarProvider>
46
- {!constants.hideSidebar && <AppSidebar />}
53
+ {showSidebar && <AppSidebar />}
47
54
  <main className="flex-1">
48
55
  <Outlet />
49
56
  </main>
50
57
  </SidebarProvider>
51
- <TabBar className="md:hidden" />
58
+ {showTabBar && <TabBar className="md:hidden" />}
52
59
  </div>
53
60
  );
54
61
  }
package/README.md CHANGED
@@ -22,12 +22,15 @@ npm install @stevederico/skateboard-ui
22
22
 
23
23
  ### View Components
24
24
  - **LandingView** - Landing page template
25
+ - **LandingViewSimple** - Simplified landing page template
25
26
  - **SettingsView** - Settings page template
26
27
  - **SignInView** - Authentication sign-in page
27
28
  - **SignUpView** - Authentication sign-up page
28
- - **StripeView** - Stripe payment integration
29
+ - **SignOutView** - Sign out handler
30
+ - **PaymentView** - Stripe payment integration
29
31
  - **TextView** - Text display view
30
32
  - **NotFound** - 404 error page
33
+ - **ErrorBoundary** - React error boundary wrapper
31
34
 
32
35
  ### shadcn/ui Components
33
36
  Full set of shadcn/ui primitives available at `@stevederico/skateboard-ui/shadcn/ui/*`
@@ -54,6 +57,36 @@ import { isAuthenticated, getAppKey, useAppSetup } from '@stevederico/skateboard
54
57
  import ProtectedRoute from '@stevederico/skateboard-ui/ProtectedRoute'
55
58
  ```
56
59
 
60
+ ## UI Visibility Control
61
+
62
+ ### Static Configuration (constants.json)
63
+ ```json
64
+ {
65
+ "hideSidebar": true,
66
+ "hideTabBar": true
67
+ }
68
+ ```
69
+
70
+ ### Programmatic Control
71
+ ```javascript
72
+ import {
73
+ showSidebar,
74
+ hideSidebar,
75
+ showTabBar,
76
+ hideTabBar,
77
+ setUIVisibility
78
+ } from '@stevederico/skateboard-ui/Utilities'
79
+
80
+ // Individual controls
81
+ hideSidebar()
82
+ showSidebar()
83
+ hideTabBar()
84
+ showTabBar()
85
+
86
+ // Batch control
87
+ setUIVisibility({ sidebar: false, tabBar: false })
88
+ ```
89
+
57
90
  ## Dependencies
58
91
 
59
92
  Built on:
package/SettingsView.jsx CHANGED
@@ -16,7 +16,7 @@ export default function SettingsView() {
16
16
  }
17
17
 
18
18
  return (
19
- <div className="min-h-screen bg-background relative overflow-hidden">
19
+ <div className="min-h-screen bg-background relative">
20
20
  {/* Content */}
21
21
  <div className="relative z-10">
22
22
  {/* Header */}
package/SignInView.jsx CHANGED
@@ -10,16 +10,7 @@ import {
10
10
  } from "./shadcn/ui/card"
11
11
  import { Input } from "./shadcn/ui/input"
12
12
  import { Label } from "./shadcn/ui/label"
13
- import * as LucideIcons from "lucide-react";
14
-
15
- // Dynamic Icon Component
16
- const DynamicIcon = ({ name, size = 24, color = 'currentColor', strokeWidth = 2, ...props }) => {
17
- const toPascalCase = (str) => str.split(/[-_\s]/).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join('');
18
- const possibleNames = [name, toPascalCase(name), name.charAt(0).toUpperCase() + name.slice(1)];
19
- const LucideIcon = possibleNames.find(n => LucideIcons[n]) ? LucideIcons[possibleNames.find(n => LucideIcons[n])] : null;
20
- return LucideIcon ? React.createElement(LucideIcon, { size, color, strokeWidth, ...props }) : null;
21
- };
22
-
13
+ import DynamicIcon from './DynamicIcon';
23
14
  import { useState, useEffect, useRef } from 'react';
24
15
  import { useNavigate } from 'react-router-dom';
25
16
  import { getState } from './Context.jsx';
package/SignOutView.jsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useEffect } from 'react';
2
2
  import { useNavigate } from 'react-router-dom';
3
- import { getBackendURL } from '@stevederico/skateboard-ui/Utilities';
3
+ import { getBackendURL } from './Utilities';
4
4
 
5
5
  function SignOutView() {
6
6
  const navigate = useNavigate();
package/SignUpView.jsx CHANGED
@@ -8,15 +8,7 @@ import {
8
8
  } from "./shadcn/ui/card"
9
9
  import { Input } from "./shadcn/ui/input"
10
10
  import { Label } from "./shadcn/ui/label"
11
- import * as LucideIcons from "lucide-react";
12
-
13
- // Dynamic Icon Component
14
- const DynamicIcon = ({ name, size = 24, color = 'currentColor', strokeWidth = 2, ...props }) => {
15
- const toPascalCase = (str) => str.split(/[-_\s]/).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join('');
16
- const possibleNames = [name, toPascalCase(name), name.charAt(0).toUpperCase() + name.slice(1)];
17
- const LucideIcon = possibleNames.find(n => LucideIcons[n]) ? LucideIcons[possibleNames.find(n => LucideIcons[n])] : null;
18
- return LucideIcon ? React.createElement(LucideIcon, { size, color, strokeWidth, ...props }) : null;
19
- };
11
+ import DynamicIcon from './DynamicIcon';
20
12
  import { useState, useEffect, useRef } from 'react';
21
13
  import { useNavigate } from 'react-router-dom';
22
14
  import constants from "@/constants.json";
package/TabBar.jsx CHANGED
@@ -1,27 +1,16 @@
1
1
  import React from 'react';
2
2
  import constants from "@/constants.json";
3
3
  import { Link, useLocation } from 'react-router-dom';
4
- import * as LucideIcons from "lucide-react";
5
-
6
- // Dynamic Icon Component
7
- const DynamicIcon = ({ name, size = 24, color = 'currentColor', strokeWidth = 2, ...props }) => {
8
- const toPascalCase = (str) => str.split(/[-_\s]/).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join('');
9
- const possibleNames = [name, toPascalCase(name), name.charAt(0).toUpperCase() + name.slice(1)];
10
- const LucideIcon = possibleNames.find(n => LucideIcons[n]) ? LucideIcons[possibleNames.find(n => LucideIcons[n])] : null;
11
- return LucideIcon ? React.createElement(LucideIcon, { size, color, strokeWidth, ...props }) : null;
12
- };
4
+ import DynamicIcon from './DynamicIcon';
13
5
 
14
6
  export default function TabBar() {
15
7
  const location = useLocation();
16
8
 
17
9
  return (
18
- <div style={{
19
- overflow: "hidden",
20
- overscrollBehavior: "none"
21
- }} className="fixed flex md:hidden pt-2 pb-4 bottom-0 inset-x-0 justify-around text-center border-t shadow-lg bg-background">
10
+ <div className="fixed flex md:hidden pt-2 pb-4 bottom-0 inset-x-0 justify-around text-center border-t shadow-lg bg-background">
22
11
  {constants?.pages?.map((item) => (
23
12
  <span className="px-3" key={item.title}>
24
- <Link to={`/app/${item.url.toLowerCase()}`} >
13
+ <Link to={`/app/${item.url.toLowerCase()}`} className="cursor-pointer">
25
14
  {
26
15
  location.pathname.includes(item.url.toLowerCase())
27
16
  ? (
@@ -39,7 +28,7 @@ export default function TabBar() {
39
28
  </span>
40
29
  ))}
41
30
  <span className="px-3">
42
- <Link to={`/app/settings`}>
31
+ <Link to={`/app/settings`} className="cursor-pointer">
43
32
  {
44
33
  location.pathname.includes('Settings'.toLowerCase())
45
34
  ? (
package/Utilities.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useState } from 'react';
2
+ import { getDispatch } from './Context.jsx';
2
3
 
3
4
  // Constants will be initialized by the app shell
4
5
  // Use window object to avoid module duplication issues with Vite
@@ -276,12 +277,6 @@ export async function showCheckout(email, productIndex = 0) {
276
277
  }
277
278
  }
278
279
 
279
- // Usage Limits Configuration
280
- const FREE_LIMITS = {
281
- todos: 3,
282
- messages: 10
283
- };
284
-
285
280
  export async function getRemainingUsage(action) {
286
281
  if (getConstants().noLogin === true) {
287
282
  return { remaining: -1, total: -1, isSubscriber: true };
@@ -663,4 +658,74 @@ export function useForm(initialValues, onSubmit) {
663
658
  }
664
659
 
665
660
 
661
+ // ===== UI VISIBILITY CONTROLS =====
662
+
663
+ /**
664
+ * Programmatically show/hide the sidebar
665
+ * @param {boolean} visible - Whether sidebar should be visible
666
+ */
667
+ export function setSidebarVisible(visible) {
668
+ const dispatch = getDispatch();
669
+ if (dispatch) {
670
+ dispatch({ type: 'SET_SIDEBAR_VISIBLE', payload: visible });
671
+ } else {
672
+ console.warn('setSidebarVisible: Context not initialized');
673
+ }
674
+ }
675
+
676
+ /**
677
+ * Programmatically show/hide the tab bar
678
+ * @param {boolean} visible - Whether tab bar should be visible
679
+ */
680
+ export function setTabBarVisible(visible) {
681
+ const dispatch = getDispatch();
682
+ if (dispatch) {
683
+ dispatch({ type: 'SET_TABBAR_VISIBLE', payload: visible });
684
+ } else {
685
+ console.warn('setTabBarVisible: Context not initialized');
686
+ }
687
+ }
688
+
689
+ /**
690
+ * Show the sidebar
691
+ */
692
+ export function showSidebar() {
693
+ setSidebarVisible(true);
694
+ }
695
+
696
+ /**
697
+ * Hide the sidebar
698
+ */
699
+ export function hideSidebar() {
700
+ setSidebarVisible(false);
701
+ }
702
+
703
+ /**
704
+ * Show the tab bar
705
+ */
706
+ export function showTabBar() {
707
+ setTabBarVisible(true);
708
+ }
666
709
 
710
+ /**
711
+ * Hide the tab bar
712
+ */
713
+ export function hideTabBar() {
714
+ setTabBarVisible(false);
715
+ }
716
+
717
+ /**
718
+ * Set visibility for both sidebar and tab bar at once
719
+ * @param {object} options - { sidebar: boolean, tabBar: boolean }
720
+ */
721
+ export function setUIVisibility({ sidebar, tabBar }) {
722
+ const dispatch = getDispatch();
723
+ if (dispatch) {
724
+ const payload = {};
725
+ if (sidebar !== undefined) payload.sidebarVisible = sidebar;
726
+ if (tabBar !== undefined) payload.tabBarVisible = tabBar;
727
+ dispatch({ type: 'SET_UI_VISIBILITY', payload });
728
+ } else {
729
+ console.warn('setUIVisibility: Context not initialized');
730
+ }
731
+ }
package/ViteConfig.js CHANGED
@@ -199,12 +199,12 @@ export const dynamicManifestPlugin = () => {
199
199
  * Returns standard skateboard config with optional overrides
200
200
  */
201
201
  export async function getSkateboardViteConfig(customConfig = {}) {
202
- const [react, tailwindcss, { resolve }, path] = await Promise.all([
202
+ const [react, tailwindcss, path] = await Promise.all([
203
203
  import('@vitejs/plugin-react-swc').then(m => m.default),
204
204
  import('@tailwindcss/vite').then(m => m.default),
205
- import('node:path'),
206
205
  import('node:path')
207
206
  ]);
207
+ const { resolve } = path;
208
208
 
209
209
  return {
210
210
  plugins: [
package/index.js CHANGED
@@ -1,3 +1,2 @@
1
- export { Button } from "./components/ui/Button";
2
1
  export { default as ProtectedRoute } from "./ProtectedRoute";
3
2
  export { isAuthenticated, getAppKey, getCSRFToken, getCurrentUser, useAppSetup } from "./Utilities";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stevederico/skateboard-ui",
3
3
  "private": false,
4
- "version": "1.2.4",
4
+ "version": "1.2.6",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  "./AppSidebar": {
@@ -24,6 +24,10 @@
24
24
  "import": "./LandingView.jsx",
25
25
  "default": "./LandingView.jsx"
26
26
  },
27
+ "./LandingViewSimple": {
28
+ "import": "./LandingViewSimple.jsx",
29
+ "default": "./LandingViewSimple.jsx"
30
+ },
27
31
  "./Layout": {
28
32
  "import": "./Layout.jsx",
29
33
  "default": "./Layout.jsx"
@@ -161,7 +165,7 @@
161
165
  "peerDependencies": {
162
166
  "react": "^19.1.0",
163
167
  "react-dom": "^19.1.0",
164
- "react-router-dom": "^7.6.1"
168
+ "react-router-dom": "^7.0.0"
165
169
  },
166
170
  "overrides": {
167
171
  "@radix-ui/react-slot": "^1.2.3"