@medalsocial/meda 2.6.0 → 2.7.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.
|
@@ -44,7 +44,7 @@ export function AppShellWorkspace({ iconRail, contextRail, rightPanel, workspace
|
|
|
44
44
|
const commandRegistry = useContext(CommandRegistryContext);
|
|
45
45
|
const shell = (_jsxs("div", { className: "flex h-svh flex-col", children: [isMobile ? (_jsx(MobileHeader, { globalActions: globalActions, headerCenter: headerCenter })) : (_jsx(ShellHeader, { globalActions: globalActions, headerCenter: headerCenter, headerLeading: headerLeading, appTabsRenderLink: appTabs?.renderLink, workspaceMenuItems: workspace?.menuItems, workspaceMenuFooter: workspace?.menuFooter, showPanelToggle: resolvedRightPanel.panelViews.length > 0, panelViews: resolvedRightPanel.panelViews })), banners ? (_jsx("div", { "data-meda-banners": "", className: "flex-shrink-0", children: banners })) : (false), _jsxs("div", { className: "relative flex flex-1 overflow-hidden", children: [!isMobile && iconRail && (_jsx(IconRail, { mainItems: iconRail.mainItems, utilityItems: iconRail.utilityItems, footer: iconRail.footer, activeId: iconRail.activeId, renderLink: iconRail.renderLink, labelVisibility: iconRail.labelVisibility })), !isMobile && contextRail && (_jsx(ContextRail, { appId: contextRail.appId, module: contextRail.module, activeItemId: contextRail.activeItemId, renderLink: contextRail.renderLink, header: contextRail.header, scroll: contextRail.scroll })), _jsx(ShellMain, { layout: mainLayout ?? 'workspace', className: cn(mainClassName,
|
|
46
46
|
// Pad the scroll area so the floating dock never covers content.
|
|
47
|
-
isMobile && mobileNav && 'pb-[calc(env(safe-area-inset-bottom)+80px)]'), children: children }), !isMobile && resolvedRightPanel.panelViews.length > 0 && (_jsx(RightPanel, { panelViews: staticPanelViews, defaultView: rightPanel?.defaultView }))] }), isMobile && mobileNav ? (_jsxs(_Fragment, { children: [_jsx(MobileDock, { items: mobileNav.dock, activeTo: mobileNav.activeTo, renderLink: mobileNav.renderLink }), _jsx(MobileWorkspaceSheet, { tree: mobileNav.tree, activeTo: mobileNav.activeTo, renderLink: mobileNav.renderLink, workspaceMenuItems: workspace?.menuItems, workspaceMenuFooter: workspace?.menuFooter })] })) : (_jsxs(_Fragment, { children: [isMobile && hasDrawerContent && _jsx(MobileBottomNav, { items: navItems }), isMobile && hasDrawerContent && (_jsx(MobileDrawers, { menuItems: mobileMenuItems, menuActiveId: iconRail?.activeId, menuRenderLink: iconRail?.renderLink, workspaceMenuItems: workspace?.menuItems, workspaceMenuFooter: workspace?.menuFooter, module: contextRail?.module, moduleAppId: contextRail?.appId, moduleActiveItemId: contextRail?.activeItemId, moduleRenderLink: contextRail?.renderLink, moduleHeader: contextRail?.header, moduleScroll: contextRail?.scroll, sectionTabs: headerLeading, panelViews: resolvedRightPanel.panelViews, defaultView: resolvedRightPanel.defaultView }))] }))] }));
|
|
47
|
+
isMobile && mobileNav && 'pb-[calc(env(safe-area-inset-bottom)+80px)]'), children: children }), !isMobile && resolvedRightPanel.panelViews.length > 0 && (_jsx(RightPanel, { panelViews: staticPanelViews, defaultView: rightPanel?.defaultView }))] }), isMobile && mobileNav ? (_jsxs(_Fragment, { children: [_jsx(MobileDock, { items: mobileNav.dock, activeTo: mobileNav.activeTo, renderLink: mobileNav.renderLink, variant: mobileNav.variant }), _jsx(MobileWorkspaceSheet, { tree: mobileNav.tree, activeTo: mobileNav.activeTo, renderLink: mobileNav.renderLink, workspaceMenuItems: workspace?.menuItems, workspaceMenuFooter: workspace?.menuFooter })] })) : (_jsxs(_Fragment, { children: [isMobile && hasDrawerContent && _jsx(MobileBottomNav, { items: navItems }), isMobile && hasDrawerContent && (_jsx(MobileDrawers, { menuItems: mobileMenuItems, menuActiveId: iconRail?.activeId, menuRenderLink: iconRail?.renderLink, workspaceMenuItems: workspace?.menuItems, workspaceMenuFooter: workspace?.menuFooter, module: contextRail?.module, moduleAppId: contextRail?.appId, moduleActiveItemId: contextRail?.activeItemId, moduleRenderLink: contextRail?.renderLink, moduleHeader: contextRail?.header, moduleScroll: contextRail?.scroll, sectionTabs: headerLeading, panelViews: resolvedRightPanel.panelViews, defaultView: resolvedRightPanel.defaultView }))] }))] }));
|
|
48
48
|
// Backwards-compatible default: meda provides the built-in CommandPalette
|
|
49
49
|
// (and its CommandRegistryContext) so consumers calling useCommands()/
|
|
50
50
|
// useCommandGroup() under the workspace shell keep working. When a registry is
|
|
@@ -7,11 +7,18 @@ export interface MobileDockProps {
|
|
|
7
7
|
activeTo?: string;
|
|
8
8
|
renderLink?: (args: MobileNavLinkArgs) => ReactNode;
|
|
9
9
|
className?: string;
|
|
10
|
+
/** `pill` (default) floating dock, or `bar` full-width labeled bottom bar. */
|
|
11
|
+
variant?: 'pill' | 'bar';
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* (
|
|
15
|
-
*
|
|
14
|
+
* Mobile dock — pinned destinations plus a workspace-sheet trigger. Two shapes:
|
|
15
|
+
*
|
|
16
|
+
* - `pill` (default): a floating, centered pill of icon-only slots, with any
|
|
17
|
+
* `emphasis: 'brand'` item (e.g. Pilot) as a standalone circle beside it.
|
|
18
|
+
* - `bar`: a flat, full-width bottom bar of evenly-spread slots — each a larger
|
|
19
|
+
* icon + label with a tinted active state — mirroring the native app. The
|
|
20
|
+
* brand item sits inline as the last slot (a filled circle).
|
|
21
|
+
*
|
|
22
|
+
* Hidden on non-mobile viewports and when the right panel is fullscreen.
|
|
16
23
|
*/
|
|
17
|
-
export declare function MobileDock({ items, activeTo, renderLink, className }: MobileDockProps): import("react/jsx-runtime").JSX.Element | null;
|
|
24
|
+
export declare function MobileDock({ items, activeTo, renderLink, className, variant, }: MobileDockProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { createElement, isValidElement } from 'react';
|
|
4
4
|
import { cn } from '../../lib/utils.js';
|
|
5
5
|
import { useMedaShell } from '../shell-provider.js';
|
|
@@ -18,12 +18,17 @@ function renderIcon(icon, size) {
|
|
|
18
18
|
return null;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* (
|
|
24
|
-
*
|
|
21
|
+
* Mobile dock — pinned destinations plus a workspace-sheet trigger. Two shapes:
|
|
22
|
+
*
|
|
23
|
+
* - `pill` (default): a floating, centered pill of icon-only slots, with any
|
|
24
|
+
* `emphasis: 'brand'` item (e.g. Pilot) as a standalone circle beside it.
|
|
25
|
+
* - `bar`: a flat, full-width bottom bar of evenly-spread slots — each a larger
|
|
26
|
+
* icon + label with a tinted active state — mirroring the native app. The
|
|
27
|
+
* brand item sits inline as the last slot (a filled circle).
|
|
28
|
+
*
|
|
29
|
+
* Hidden on non-mobile viewports and when the right panel is fullscreen.
|
|
25
30
|
*/
|
|
26
|
-
export function MobileDock({ items, activeTo, renderLink, className }) {
|
|
31
|
+
export function MobileDock({ items, activeTo, renderLink, className, variant = 'pill', }) {
|
|
27
32
|
const ctx = useMedaShell();
|
|
28
33
|
const band = useShellViewport();
|
|
29
34
|
if (band !== 'mobile')
|
|
@@ -32,8 +37,6 @@ export function MobileDock({ items, activeTo, renderLink, className }) {
|
|
|
32
37
|
return null;
|
|
33
38
|
if (items.length === 0)
|
|
34
39
|
return null;
|
|
35
|
-
const pillItems = items.filter((item) => item.emphasis !== 'brand');
|
|
36
|
-
const brandItems = items.filter((item) => item.emphasis === 'brand');
|
|
37
40
|
const runAction = (item) => {
|
|
38
41
|
if (item.action === 'open-sheet')
|
|
39
42
|
ctx.mobileDrawer.setOpen(WORKSPACE_SHEET_KEY);
|
|
@@ -42,6 +45,45 @@ export function MobileDock({ items, activeTo, renderLink, className }) {
|
|
|
42
45
|
else if (item.action === 'open-command-palette')
|
|
43
46
|
ctx.commandPalette.setOpen(true);
|
|
44
47
|
};
|
|
48
|
+
// ── Full-width bar (native-app style) ──────────────────────────────────
|
|
49
|
+
if (variant === 'bar') {
|
|
50
|
+
const renderBarSlot = (item) => {
|
|
51
|
+
const label = typeof item.label === 'function' ? item.label() : item.label;
|
|
52
|
+
const isActive = Boolean(item.to && activeTo && item.to === activeTo);
|
|
53
|
+
const isBrand = item.emphasis === 'brand';
|
|
54
|
+
const iconEl = isBrand ? (_jsx("span", { className: "flex size-8 items-center justify-center rounded-full bg-[#5B2D8C] text-white", children: renderIcon(item.icon, 20) })) : (renderIcon(item.icon, 25));
|
|
55
|
+
let toneClass = 'text-muted-foreground hover:text-foreground';
|
|
56
|
+
if (isBrand)
|
|
57
|
+
toneClass = 'text-muted-foreground';
|
|
58
|
+
else if (isActive)
|
|
59
|
+
toneClass = 'font-medium text-primary';
|
|
60
|
+
const slotClass = cn('flex w-full flex-col items-center justify-center gap-1 py-2 text-[11px] leading-none transition-colors', toneClass);
|
|
61
|
+
const children = (_jsxs(_Fragment, { children: [_jsxs("span", { className: "relative flex items-center justify-center", children: [iconEl, item.badge ? (_jsx("span", { className: "absolute -top-0.5 -right-1 size-2 rounded-full bg-primary ring-2 ring-card" })) : null] }), _jsx("span", { children: label })] }));
|
|
62
|
+
if (item.to && !item.action) {
|
|
63
|
+
if (renderLink) {
|
|
64
|
+
return renderLink({
|
|
65
|
+
to: item.to,
|
|
66
|
+
isActive,
|
|
67
|
+
className: slotClass,
|
|
68
|
+
children,
|
|
69
|
+
onNavigate: () => undefined,
|
|
70
|
+
linkProps: {
|
|
71
|
+
href: item.to,
|
|
72
|
+
className: slotClass,
|
|
73
|
+
'aria-label': label,
|
|
74
|
+
'aria-current': isActive ? 'page' : undefined,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return (_jsx("a", { href: item.to, className: slotClass, "aria-label": label, "aria-current": isActive ? 'page' : undefined, children: children }));
|
|
79
|
+
}
|
|
80
|
+
return (_jsx("button", { type: "button", onClick: () => runAction(item), "aria-label": label, className: slotClass, children: children }));
|
|
81
|
+
};
|
|
82
|
+
return (_jsx("nav", { "data-testid": "mobile-dock", "aria-label": "Dock", className: cn('absolute inset-x-0 bottom-0 z-30 flex items-stretch border-t border-border bg-card pb-[env(safe-area-inset-bottom)]', className), children: items.map((item) => (_jsx("div", { className: "flex flex-1", children: renderBarSlot(item) }, item.id))) }));
|
|
83
|
+
}
|
|
84
|
+
// ── Floating pill (default) ────────────────────────────────────────────
|
|
85
|
+
const pillItems = items.filter((item) => item.emphasis !== 'brand');
|
|
86
|
+
const brandItems = items.filter((item) => item.emphasis === 'brand');
|
|
45
87
|
const renderSlot = (item, size) => {
|
|
46
88
|
const label = typeof item.label === 'function' ? item.label() : item.label;
|
|
47
89
|
const isActive = Boolean(item.to && activeTo && item.to === activeTo);
|
package/dist/shell/types.d.ts
CHANGED
|
@@ -118,6 +118,14 @@ export interface AppShellMobileNavConfig {
|
|
|
118
118
|
activeTo?: string;
|
|
119
119
|
/** Render dock/sheet targets as router links (else plain `<a>`). */
|
|
120
120
|
renderLink?: (args: MobileNavLinkArgs) => ReactNode;
|
|
121
|
+
/**
|
|
122
|
+
* Dock presentation:
|
|
123
|
+
* - `pill` (default) — a floating, centered pill of icon-only slots with any
|
|
124
|
+
* `emphasis: 'brand'` item as a standalone circle beside it.
|
|
125
|
+
* - `bar` — a flat, full-width bottom bar of evenly-spread slots, each with a
|
|
126
|
+
* larger icon + label and a tinted active state (mirrors the native app).
|
|
127
|
+
*/
|
|
128
|
+
variant?: 'pill' | 'bar';
|
|
121
129
|
}
|
|
122
130
|
export interface ShellRenderContext {
|
|
123
131
|
workspaceId: string;
|