@pablozaiden/webapp 0.3.3 → 0.3.4
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/package.json +1 -1
- package/src/web/WebAppRoot.tsx +44 -2
- package/src/web/styles.css +6 -3
package/package.json
CHANGED
package/src/web/WebAppRoot.tsx
CHANGED
|
@@ -70,6 +70,18 @@ type StoredSidebarPin = {
|
|
|
70
70
|
route: WebAppRoute;
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
function isSidebarShortcutEditableTarget(target: EventTarget | null): boolean {
|
|
74
|
+
if (!(target instanceof HTMLElement)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (target.isContentEditable || target.closest("[contenteditable=''], [contenteditable='true']")) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return target instanceof HTMLInputElement
|
|
81
|
+
|| target instanceof HTMLTextAreaElement
|
|
82
|
+
|| target instanceof HTMLSelectElement;
|
|
83
|
+
}
|
|
84
|
+
|
|
73
85
|
function routeToHash(route: WebAppRoute): string {
|
|
74
86
|
const params = new URLSearchParams();
|
|
75
87
|
for (const [key, value] of Object.entries(route)) {
|
|
@@ -980,6 +992,13 @@ export function WebAppRoot({ appName, homeRoute, sidebar, routes, header, onRout
|
|
|
980
992
|
const [search, setSearch] = useState("");
|
|
981
993
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
982
994
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
995
|
+
const toggleSidebarCollapsed = useCallback(() => {
|
|
996
|
+
setSidebarCollapsed((current) => {
|
|
997
|
+
const nextCollapsed = !current;
|
|
998
|
+
setSidebarOpen(!nextCollapsed);
|
|
999
|
+
return nextCollapsed;
|
|
1000
|
+
});
|
|
1001
|
+
}, []);
|
|
983
1002
|
const sidebarSearchEnabled = sidebar.search !== false;
|
|
984
1003
|
const pinningEnabled = sidebar.pinning !== false;
|
|
985
1004
|
const sidebarPins = useSidebarPins(appName, sidebar.pinning ? sidebar.pinning.storageKey : undefined);
|
|
@@ -1041,6 +1060,28 @@ export function WebAppRoot({ appName, homeRoute, sidebar, routes, header, onRout
|
|
|
1041
1060
|
.catch(() => undefined);
|
|
1042
1061
|
}, [config?.currentUser?.id, setTheme]);
|
|
1043
1062
|
|
|
1063
|
+
useEffect(() => {
|
|
1064
|
+
function handleSidebarShortcut(event: KeyboardEvent) {
|
|
1065
|
+
if (
|
|
1066
|
+
event.key.toLowerCase() !== "b"
|
|
1067
|
+
|| event.altKey
|
|
1068
|
+
|| event.shiftKey
|
|
1069
|
+
|| event.ctrlKey === event.metaKey
|
|
1070
|
+
|| event.isComposing
|
|
1071
|
+
|| event.repeat
|
|
1072
|
+
|| isSidebarShortcutEditableTarget(event.target)
|
|
1073
|
+
) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
event.preventDefault();
|
|
1078
|
+
toggleSidebarCollapsed();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
document.addEventListener("keydown", handleSidebarShortcut);
|
|
1082
|
+
return () => document.removeEventListener("keydown", handleSidebarShortcut);
|
|
1083
|
+
}, [toggleSidebarCollapsed]);
|
|
1084
|
+
|
|
1044
1085
|
useEffect(() => {
|
|
1045
1086
|
onRouteChange?.(route);
|
|
1046
1087
|
}, [onRouteChange, route]);
|
|
@@ -1084,6 +1125,7 @@ export function WebAppRoot({ appName, homeRoute, sidebar, routes, header, onRout
|
|
|
1084
1125
|
const headerTitle = header?.renderTitle?.(headerContext) ?? defaultTitle;
|
|
1085
1126
|
const primaryHeaderActions = header?.renderActions?.(headerContext);
|
|
1086
1127
|
const headerActionLabel = typeof headerTitle === "string" ? headerTitle : defaultTitle;
|
|
1128
|
+
const sidebarToggleLabel = sidebarCollapsed ? "Show sidebar" : "Collapse sidebar";
|
|
1087
1129
|
const navigateFromSidebarHeader = (nextRoute: WebAppRoute) => {
|
|
1088
1130
|
navigate(nextRoute);
|
|
1089
1131
|
setSidebarOpen(false);
|
|
@@ -1106,7 +1148,7 @@ export function WebAppRoot({ appName, homeRoute, sidebar, routes, header, onRout
|
|
|
1106
1148
|
<div className="wapp-sidebar-actions">
|
|
1107
1149
|
{topActions.map((action) => <IconButton key={action.id} className="wapp-sidebar-top-button" title={action.title} aria-label={action.title} onClick={() => runSidebarHeaderAction(action)}><ActionIcon icon={action.icon} /></IconButton>)}
|
|
1108
1150
|
<IconButton className="wapp-sidebar-top-button" title="Settings" aria-label="Open settings" active={route.view === "settings"} onClick={() => navigateFromSidebarHeader({ view: "settings" })}><Icon name="settings" /></IconButton>
|
|
1109
|
-
<IconButton className="wapp-sidebar-top-button" title=
|
|
1151
|
+
<IconButton className="wapp-sidebar-top-button" title={sidebarToggleLabel} aria-label={sidebarToggleLabel} onClick={toggleSidebarCollapsed}><Icon name="sidebar" /></IconButton>
|
|
1110
1152
|
</div>
|
|
1111
1153
|
</div>
|
|
1112
1154
|
<div className="wapp-sidebar-scroll">
|
|
@@ -1118,7 +1160,7 @@ export function WebAppRoot({ appName, homeRoute, sidebar, routes, header, onRout
|
|
|
1118
1160
|
<section className="wapp-main">
|
|
1119
1161
|
<header className="wapp-main-header">
|
|
1120
1162
|
<div className="wapp-main-header-title">
|
|
1121
|
-
{sidebarCollapsed ? <IconButton className="wapp-sidebar-top-button" aria-label=
|
|
1163
|
+
{sidebarCollapsed ? <IconButton className="wapp-sidebar-top-button" aria-label={sidebarToggleLabel} title={sidebarToggleLabel} onClick={toggleSidebarCollapsed}><Icon name="sidebar" /></IconButton> : <IconButton className="wapp-mobile-only wapp-sidebar-top-button" aria-label="Show sidebar" title="Show sidebar" onClick={() => setSidebarOpen(true)}><Icon name="sidebar" /></IconButton>}
|
|
1122
1164
|
<h1>{headerTitle}</h1>
|
|
1123
1165
|
</div>
|
|
1124
1166
|
{primaryHeaderActions || headerActions.length ? (
|
package/src/web/styles.css
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
--wapp-danger-bg: #991b1b;
|
|
14
14
|
--wapp-primary: #111827;
|
|
15
15
|
--wapp-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
16
|
+
--wapp-overlay-bg: rgb(0 0 0 / 0.5);
|
|
17
|
+
--wapp-overlay-blur: blur(4px);
|
|
16
18
|
--wapp-safe-area-top: env(safe-area-inset-top, 0px);
|
|
17
19
|
--wapp-safe-area-bottom: env(safe-area-inset-bottom, 0px);
|
|
18
20
|
--wapp-safe-area-left: env(safe-area-inset-left, 0px);
|
|
@@ -1549,7 +1551,8 @@ textarea::placeholder {
|
|
|
1549
1551
|
.wapp-mobile-backdrop {
|
|
1550
1552
|
position: fixed;
|
|
1551
1553
|
inset: 0;
|
|
1552
|
-
background:
|
|
1554
|
+
background: var(--wapp-overlay-bg);
|
|
1555
|
+
backdrop-filter: var(--wapp-overlay-blur);
|
|
1553
1556
|
z-index: 50;
|
|
1554
1557
|
}
|
|
1555
1558
|
|
|
@@ -1650,8 +1653,8 @@ textarea::placeholder {
|
|
|
1650
1653
|
.wapp-modal-overlay {
|
|
1651
1654
|
position: absolute;
|
|
1652
1655
|
inset: 0;
|
|
1653
|
-
background:
|
|
1654
|
-
backdrop-filter: blur
|
|
1656
|
+
background: var(--wapp-overlay-bg);
|
|
1657
|
+
backdrop-filter: var(--wapp-overlay-blur);
|
|
1655
1658
|
}
|
|
1656
1659
|
|
|
1657
1660
|
.wapp-modal {
|