enhance(ui): refactor mobile UI components for improved readability and maintainability

This commit is contained in:
charlie
2025-08-12 16:15:34 +08:00
parent c0dc53c3f5
commit 1de207a0ca
3 changed files with 216 additions and 199 deletions

View File

@@ -6,7 +6,7 @@ import React, {
useMemo,
useRef,
useState,
} from "react";
} from 'react'
import {
Sheet,
SheetStack,
@@ -15,14 +15,14 @@ import {
usePageScrollData,
SheetViewProps,
createComponentId,
} from "@silk-hq/components";
import "./SheetWithDepth.css";
} from '@silk-hq/components'
import './SheetWithDepth.css'
// ================================================================================================
// Stack Id
// ================================================================================================
const sheetWithDepthStackId = createComponentId();
const sheetWithDepthStackId = createComponentId()
// ================================================================================================
// StackRoot Context
@@ -37,16 +37,16 @@ type SheetWithDepthStackRootContextValue = {
const SheetWithDepthStackRootContext = createContext<SheetWithDepthStackRootContextValue | null>(
null
);
)
const useSheetWithDepthStackRootContext = () => {
const context = useContext(SheetWithDepthStackRootContext);
const context = useContext(SheetWithDepthStackRootContext)
if (!context) {
throw new Error(
"useSheetWithDepthStackRootContext must be used within a SheetWithDepthStackRootContext"
);
'useSheetWithDepthStackRootContext must be used within a SheetWithDepthStackRootContext'
)
}
return context;
};
return context
}
// ================================================================================================
// View Context
@@ -54,14 +54,14 @@ const useSheetWithDepthStackRootContext = () => {
const SheetWithDepthViewContext = createContext<{
indexInStack: number;
} | null>(null);
} | null>(null)
const useSheetWithDepthViewContext = () => {
const context = useContext(SheetWithDepthViewContext);
const context = useContext(SheetWithDepthViewContext)
if (!context) {
throw new Error("useSheetWithDepthViewContext must be used within a SheetWithDepthViewContext");
throw new Error('useSheetWithDepthViewContext must be used within a SheetWithDepthViewContext')
}
return context;
};
return context
}
// ================================================================================================
// StackRoot
@@ -71,10 +71,10 @@ const SheetWithDepthStackRoot = React.forwardRef<
React.ElementRef<typeof SheetStack.Root>,
React.ComponentProps<typeof SheetStack.Root>
>(({ children, ...restProps }, ref) => {
const stackBackgroundRef = useRef<HTMLDivElement | null>(null);
const stackFirstSheetBackdropRef = useRef<HTMLDivElement | null>(null);
const stackBackgroundRef = useRef<HTMLDivElement | null>(null)
const stackFirstSheetBackdropRef = useRef<HTMLDivElement | null>(null)
const [stackingCount, setStackingCount] = useState(0);
const [stackingCount, setStackingCount] = useState(0)
const contextValue = useMemo(
() => ({
@@ -84,7 +84,7 @@ const SheetWithDepthStackRoot = React.forwardRef<
setStackingCount,
}),
[stackingCount]
);
)
return (
<SheetWithDepthStackRootContext.Provider value={contextValue}>
@@ -92,9 +92,9 @@ const SheetWithDepthStackRoot = React.forwardRef<
{children}
</SheetStack.Root>
</SheetWithDepthStackRootContext.Provider>
);
});
SheetWithDepthStackRoot.displayName = "SheetWithDepthStack.Root";
)
})
SheetWithDepthStackRoot.displayName = 'SheetWithDepthStack.Root'
// ================================================================================================
// StackSceneryOutlets
@@ -103,27 +103,27 @@ SheetWithDepthStackRoot.displayName = "SheetWithDepthStack.Root";
// The SheetStack outlets that define the scenery of the stack
// (i.e. the content underneath) for the depth effect.
const initialTopOffset = "max(env(safe-area-inset-top), 1.3vh)";
const initialTopOffset = 'max(env(safe-area-inset-top), 1.3vh)'
const SheetWithDepthStackSceneryOutlets = React.forwardRef<
React.ElementRef<typeof SheetStack.Outlet>,
Omit<React.ComponentProps<typeof SheetStack.Outlet>, "asChild">
Omit<React.ComponentProps<typeof SheetStack.Outlet>, 'asChild'>
>(({ children, className, stackingAnimation: stackingAnimationFromProps, ...restProps }, ref) => {
const { stackBackgroundRef, stackFirstSheetBackdropRef } = useSheetWithDepthStackRootContext();
const { stackBackgroundRef, stackFirstSheetBackdropRef } = useSheetWithDepthStackRootContext()
const { nativePageScrollReplaced } = usePageScrollData();
const { nativePageScrollReplaced } = usePageScrollData()
const [iOSStandalone, setiOSStandalone] = useState(false);
const [iOSStandalone, setiOSStandalone] = useState(false)
useEffect(() => {
setiOSStandalone(
// @ts-ignore
window.navigator.standalone && window.navigator.userAgent?.match(/iPhone|iPad/i)
);
}, []);
)
}, [])
const stackingAnimation: React.ComponentPropsWithoutRef<
typeof Sheet.Outlet
>["stackingAnimation"] = {
>['stackingAnimation'] = {
// Clipping & border-radius. We have a different animation
// when the native page scroll is replaced, and in iOS
// standalone mode.
@@ -133,39 +133,39 @@ const SheetWithDepthStackSceneryOutlets = React.forwardRef<
// border-radius because the corners are hidden by the
// screen corners. So we just set the border-radius to
// the needed value.
{
overflow: "clip",
borderRadius: "24px",
transformOrigin: "50% 0",
}
{
overflow: 'clip',
borderRadius: '24px',
transformOrigin: '50% 0',
}
: // Outside of iOS standalone mode we do animate
// the border-radius because the scenery is a visible
// rectangle.
{
overflow: "clip",
borderRadius: ({ progress }: any) => Math.min(progress * 24, 24) + "px",
transformOrigin: "50% 0",
}
{
overflow: 'clip',
borderRadius: ({ progress }: any) => Math.min(progress * 24, 24) + 'px',
transformOrigin: '50% 0',
}
: // When the native page scroll is not replaced we
// need to use the Silk's special clip properties to cut
// off the rest of the page.
{
clipBoundary: "layout-viewport",
clipBorderRadius: "24px",
clipTransformOrigin: "50% 0",
}),
{
clipBoundary: 'layout-viewport',
clipBorderRadius: '24px',
clipTransformOrigin: '50% 0',
}),
// Translate & scale
translateY: ({ progress }) =>
progress <= 1
? "calc(" + progress + " * " + initialTopOffset + ")"
? 'calc(' + progress + ' * ' + initialTopOffset + ')'
: // prettier-ignore
"calc(" + initialTopOffset + " + 0.65vh * " + (progress - 1) + ")",
'calc(' + initialTopOffset + ' + 0.65vh * ' + (progress - 1) + ')',
scale: [1, 0.91],
// We merge animations coming from the props
...stackingAnimationFromProps,
};
}
return (
<>
@@ -176,7 +176,7 @@ const SheetWithDepthStackSceneryOutlets = React.forwardRef<
/>
{/* Element used as a container for the content under the stack. */}
<SheetStack.Outlet
className={`SheetWithDepth-stackSceneryContainer ${className ?? ""}`.trim()}
className={`SheetWithDepth-stackSceneryContainer ${className ?? ''}`.trim()}
forComponent={sheetWithDepthStackId}
stackingAnimation={stackingAnimation}
{...restProps}
@@ -190,17 +190,17 @@ const SheetWithDepthStackSceneryOutlets = React.forwardRef<
/>
</SheetStack.Outlet>
</>
);
});
SheetWithDepthStackSceneryOutlets.displayName = "SheetWithDepthStack.SceneryOutlets";
)
})
SheetWithDepthStackSceneryOutlets.displayName = 'SheetWithDepthStack.SceneryOutlets'
// ================================================================================================
// Root
// ================================================================================================
type SheetRootProps = React.ComponentPropsWithoutRef<typeof Sheet.Root>;
type SheetWithDepthRootProps = Omit<SheetRootProps, "license"> & {
license?: SheetRootProps["license"];
type SheetWithDepthRootProps = Omit<SheetRootProps, 'license'> & {
license?: SheetRootProps['license'];
};
const SheetWithDepthRoot = React.forwardRef<
@@ -208,10 +208,10 @@ const SheetWithDepthRoot = React.forwardRef<
SheetWithDepthRootProps
>((props, ref) => {
return (
<Sheet.Root license="commercial" forComponent={sheetWithDepthStackId} {...props} ref={ref} />
);
});
SheetWithDepthRoot.displayName = "SheetWithDepth.Root";
<Sheet.Root license="commercial" forComponent={sheetWithDepthStackId} {...props} ref={ref}/>
)
})
SheetWithDepthRoot.displayName = 'SheetWithDepth.Root'
// ================================================================================================
// View
@@ -237,58 +237,58 @@ const SheetWithDepthView = React.forwardRef<
stackBackgroundRef,
stackFirstSheetBackdropRef,
} = useSheetWithDepthStackRootContext();
} = useSheetWithDepthStackRootContext()
const [indexInStack, setIndexInStack] = useState(0);
const [travelStatus, setTravelStatus] = useState("idleOutside");
const [indexInStack, setIndexInStack] = useState(0)
const [travelStatus, setTravelStatus] = useState('idleOutside')
//
// Define a dimming overlay
const { setDimmingOverlayOpacity, animateDimmingOverlayOpacity } = useThemeColorDimmingOverlay({
elementRef: stackBackgroundRef,
dimmingColor: "rgb(0, 0, 0)",
});
dimmingColor: 'rgb(0, 0, 0)',
})
//
// travelStatusChangeHandler
const travelStatusChangeHandler = useCallback<
NonNullable<SheetViewProps["onTravelStatusChange"]>
NonNullable<SheetViewProps['onTravelStatusChange']>
>(
(newTravelStatus) => {
// Set indexInStack & stackingCount
if (travelStatus !== "stepping" && newTravelStatus === "idleInside") {
setStackingCount((prevStackingCount: number) => prevStackingCount + 1);
if (travelStatus !== 'stepping' && newTravelStatus === 'idleInside') {
setStackingCount((prevStackingCount: number) => prevStackingCount + 1)
if (indexInStack === 0) {
setIndexInStack(stackingCount + 1);
setIndexInStack(stackingCount + 1)
}
}
//
else if (newTravelStatus === "idleOutside") {
setStackingCount((prevStackingCount: number) => prevStackingCount - 1);
setIndexInStack(0);
else if (newTravelStatus === 'idleOutside') {
setStackingCount((prevStackingCount: number) => prevStackingCount - 1)
setIndexInStack(0)
}
// Animate on entering
if (newTravelStatus === "entering" && stackingCount === 0) {
animateDimmingOverlayOpacity({ keyframes: [0, 1] });
if (newTravelStatus === 'entering' && stackingCount === 0) {
animateDimmingOverlayOpacity({ keyframes: [0, 1] })
animate(stackFirstSheetBackdropRef.current as HTMLElement, {
opacity: [0, 0.33],
});
})
}
// Animate on exiting
if (newTravelStatus === "exiting" && stackingCount === 1) {
animateDimmingOverlayOpacity({ keyframes: [1, 0] });
if (newTravelStatus === 'exiting' && stackingCount === 1) {
animateDimmingOverlayOpacity({ keyframes: [1, 0] })
animate(stackFirstSheetBackdropRef.current as HTMLElement, {
opacity: [0.33, 0],
});
})
}
// Set the state
onTravelStatusChange?.(newTravelStatus);
setTravelStatus(newTravelStatus);
onTravelStatusChange?.(newTravelStatus)
setTravelStatus(newTravelStatus)
},
[
travelStatus,
@@ -299,26 +299,26 @@ const SheetWithDepthView = React.forwardRef<
animateDimmingOverlayOpacity,
onTravelStatusChange,
]
);
)
//
// travelHandler
const travelHandler = useMemo(() => {
if (indexInStack === 1 && travelStatus !== "entering" && travelStatus !== "exiting") {
const handler: NonNullable<SheetViewProps["onTravel"]> = ({ progress, ...rest }) => {
setDimmingOverlayOpacity(progress);
if (indexInStack === 1 && travelStatus !== 'entering' && travelStatus !== 'exiting') {
const handler: NonNullable<SheetViewProps['onTravel']> = ({ progress, ...rest }) => {
setDimmingOverlayOpacity(progress)
stackFirstSheetBackdropRef.current?.style.setProperty(
"opacity",
'opacity',
(progress * 0.33) as unknown as string
);
travelHandlerFromProps?.({ progress, ...rest });
};
return handler;
)
travelHandlerFromProps?.({ progress, ...rest })
}
return handler
} else {
return travelHandlerFromProps;
return travelHandlerFromProps
}
}, [indexInStack, travelStatus, stackFirstSheetBackdropRef, setDimmingOverlayOpacity]);
}, [indexInStack, travelStatus, stackFirstSheetBackdropRef, setDimmingOverlayOpacity])
//
// Return
@@ -326,7 +326,7 @@ const SheetWithDepthView = React.forwardRef<
return (
<SheetWithDepthViewContext.Provider value={{ indexInStack }}>
<Sheet.View
className={`SheetWithDepth-view ${className ?? ""}`.trim()}
className={`SheetWithDepth-view ${className ?? ''}`.trim()}
contentPlacement="bottom"
onTravelStatusChange={travelStatusChangeHandler}
onTravel={travelHandler}
@@ -337,10 +337,10 @@ const SheetWithDepthView = React.forwardRef<
{children}
</Sheet.View>
</SheetWithDepthViewContext.Provider>
);
)
}
);
SheetWithDepthView.displayName = "SheetWithDepth.View";
)
SheetWithDepthView.displayName = 'SheetWithDepth.View'
// ================================================================================================
// Backdrop
@@ -351,8 +351,8 @@ const SheetWithDepthBackdrop = React.forwardRef<
React.ComponentPropsWithoutRef<typeof Sheet.Backdrop>
// @ts-ignore
>(({ className, ...restProps }, ref) => {
const { stackingCount } = useSheetWithDepthStackRootContext();
const { indexInStack } = useSheetWithDepthViewContext();
const { stackingCount } = useSheetWithDepthStackRootContext()
const { indexInStack } = useSheetWithDepthViewContext()
return (
// We don't render the Backdrop for the first sheet in the
@@ -361,15 +361,15 @@ const SheetWithDepthBackdrop = React.forwardRef<
stackingCount > 0 &&
indexInStack !== 1 && (
<Sheet.Backdrop
className={`SheetWithDepth-backdrop ${className ?? ""}`.trim()}
className={`SheetWithDepth-backdrop ${className ?? ''}`.trim()}
travelAnimation={{ opacity: [0, 0.33] }}
{...restProps}
ref={ref}
/>
)
);
});
SheetWithDepthBackdrop.displayName = "SheetWithDepth.Backdrop";
)
})
SheetWithDepthBackdrop.displayName = 'SheetWithDepth.Backdrop'
// ================================================================================================
// Content
@@ -381,42 +381,42 @@ const SheetWithDepthContent = React.forwardRef<
>(({ children, className, stackingAnimation, ...restProps }, ref) => {
return (
<Sheet.Content
className={`SheetWithDepth-content ${className ?? ""}`.trim()}
className={`SheetWithDepth-content ${className ?? ''}`.trim()}
stackingAnimation={{
translateY: ({ progress }) =>
progress <= 1
? progress * -1.3 + "vh"
? progress * -1.3 + 'vh'
: // prettier-ignore
"calc(-1.3vh + 0.65vh * " + (progress - 1) + ")",
'calc(-1.3vh + 0.65vh * ' + (progress - 1) + ')',
scale: [1, 0.91],
transformOrigin: "50% 0",
transformOrigin: '50% 0',
...stackingAnimation,
}}
{...restProps}
ref={ref}
>
<Sheet.BleedingBackground className="SheetWithDepth-bleedingBackground" />
<Sheet.BleedingBackground className="SheetWithDepth-bleedingBackground"/>
{children}
</Sheet.Content>
);
});
SheetWithDepthContent.displayName = "SheetWithDepth.Content";
)
})
SheetWithDepthContent.displayName = 'SheetWithDepth.Content'
// ================================================================================================
// Unchanged components
// ================================================================================================
const SheetWithDepthPortal = Sheet.Portal;
const SheetWithDepthTrigger = Sheet.Trigger;
const SheetWithDepthHandle = Sheet.Handle;
const SheetWithDepthOutlet = Sheet.Outlet;
const SheetWithDepthTitle = Sheet.Title;
const SheetWithDepthDescription = Sheet.Description;
const SheetWithDepthPortal = Sheet.Portal
const SheetWithDepthTrigger = Sheet.Trigger
const SheetWithDepthHandle = Sheet.Handle
const SheetWithDepthOutlet = Sheet.Outlet
const SheetWithDepthTitle = Sheet.Title
const SheetWithDepthDescription = Sheet.Description
export const SheetWithDepthStack = {
Root: SheetWithDepthStackRoot,
SceneryOutlets: SheetWithDepthStackSceneryOutlets,
};
}
export const SheetWithDepth = {
Root: SheetWithDepthRoot,
@@ -429,4 +429,4 @@ export const SheetWithDepth = {
Outlet: SheetWithDepthOutlet,
Title: SheetWithDepthTitle,
Description: SheetWithDepthDescription,
};
}

View File

@@ -5,7 +5,7 @@
:root {
--ls-page-title-size: 26px;
--silk-topbar-height: 48px;
--silk-topbar-inner-height: 32px;
--silk-tabbar-bottom-paddding: 12px;
}
@@ -14,11 +14,20 @@ html.is-native-ios {
}
html.is-native-android {
--silk-topbar-inner-padding-top: 10px;
--silk-topbar-inner-height: 40px;
--silk-topbar-inner-padding-bottom: 6px;
.app-silk-topbar {}
}
#mobile-editor-toolbar {
}
#main-container {
overflow-y: visible;
}
html.has-mobile-keyboard {
body {
@apply overflow-hidden
@@ -140,9 +149,7 @@ a, button {
}
}
.app-silk-sheet-scroll-content {
height: 92vh;
}
.app-silk-depth-sheet-content {}
}
.block-content-or-editor-inner {
@@ -293,8 +300,8 @@ a, button {
@apply bg-gray-01 min-h-[100svh] overflow-x-hidden;
}
.app-silk-sheet-scroll-content {
@apply flex flex-col items-center;
.app-silk-depth-sheet-content {
display: grid;
}
.BottomSheet-bleedingBackground,
@@ -315,7 +322,6 @@ a, button {
overflow: hidden;
overflow: clip;
position: relative;
display: grid;
}
html[data-silk-native-page-scroll-replaced=false] .app-silk-index-scroll-view {
@@ -329,11 +335,15 @@ html[data-silk-native-page-scroll-replaced=false] .app-silk-index-scroll-view {
@apply p-4 flex flex-col gap-3 bg-gray-01;
&[data-tab=search] {
--silk-topbar-height: 2px;
--silk-topbar-inner-height: 2px;
}
padding-top: calc(env(safe-area-inset-top, 0px) + var(--silk-topbar-height) + 10px);
padding-top: calc(env(safe-area-inset-top, 0px) + var(--silk-topbar-inner-height) + 22px);
padding-bottom: 120px;
#journals {
@apply -mt-4 px-1;
}
}
.app-silk-topbar {
@@ -348,9 +358,10 @@ html[data-silk-native-page-scroll-replaced=false] .app-silk-index-scroll-view {
}
}
padding-top: calc(env(safe-area-inset-top, 0px) + 2px);
padding-bottom: 8px;
height: 32px;
padding-top: calc(env(safe-area-inset-top, 0px) + var(--silk-topbar-inner-padding-top, 2px));
height: var(--silk-topbar-inner-height, 32px);
padding-bottom: var(--silk-topbar-inner-padding-bottom, 8px);
box-sizing: content-box;
&.search {

View File

@@ -30,79 +30,85 @@
(set-favorited! (page-handler/favorited? (str (:block/uuid block)))))
[block])
(when open?
(state/clear-edit!)
(init/keyboard-hide))
(hooks/use-effect!
(fn []
(when open?
(state/clear-edit!)
(init/keyboard-hide)))
[open?])
(silkhq/bottom-sheet
{:presented (boolean open?)
:onPresentedChange (fn [v?]
(when (false? v?)
(mobile-state/set-singleton-modal! nil)
(state/clear-edit!)
(state/pub-event! [:mobile/keyboard-will-hide])))}
(silkhq/bottom-sheet-portal
(silkhq/bottom-sheet-view
{:class "block-modal-page"
:inertOutside false}
(silkhq/bottom-sheet-backdrop)
(silkhq/bottom-sheet-content
{:class "app-silk-sheet-scroll-content"}
(silkhq/scroll {:as-child true}
(silkhq/scroll-view
{:class "app-silk-scroll-view"}
(silkhq/scroll-content
{:class "app-silk-scroll-content"}
[:div.app-silk-scroll-content-inner
[:div.flex.justify-between.items-center.block-modal-page-header
[:a.opacity-40.active:opacity-60.px-2
{:on-pointer-down close!}
(shui/tabler-icon "chevron-down" {:size 18 :stroke 3})]
(silkhq/depth-sheet
{:presented (boolean open?)
:onPresentedChange (fn [v?]
(when (false? v?)
(mobile-state/set-singleton-modal! nil)
(state/clear-edit!)
(state/pub-event! [:mobile/keyboard-will-hide])))}
(silkhq/depth-sheet-portal
(silkhq/depth-sheet-view
{:class "block-modal-page"
:inertOutside false}
(silkhq/depth-sheet-backdrop)
(silkhq/depth-sheet-content
{:class "app-silk-depth-sheet-content"}
(silkhq/scroll {:as-child true}
(silkhq/scroll-view
{:class "app-silk-scroll-view"
:scrollGestureTrap {:yEnd true}}
(silkhq/scroll-content
{:class "app-silk-scroll-content"}
[:span.flex.items-center.gap-2
(when-let [block-id-str (str (:block/uuid block))]
[:a.active:opacity-80.pr-1
{:class (if favorited? "opacity-80 !text-yellow-800" "opacity-40")
:on-click #(-> (if favorited?
(page-handler/<unfavorite-page! block-id-str)
(page-handler/<favorite-page! block-id-str))
(p/then (fn [] (set-favorited! (not favorited?)))))}
(shui/tabler-icon (if favorited? "star-filled" "star") {:size 18 :stroke 2})])
[:a.opacity-40.active:opacity-60.pr-1
{:on-pointer-down (fn []
(mobile-ui/open-popup!
(fn []
[:div.-mx-2
(ui/menu-link
{:on-click #(mobile-ui/close-popup!)}
[:span.text-lg.flex.gap-2.items-center
(shui/tabler-icon "copy" {:class "opacity-80" :size 22})
"Copy"])
[:div.app-silk-scroll-content-inner
[:div.flex.justify-between.items-center.block-modal-page-header
[:a.opacity-40.active:opacity-60.px-2
{:on-pointer-down close!}
(shui/tabler-icon "chevron-down" {:size 18 :stroke 3})]
(ui/menu-link
{:on-click #(-> (shui/dialog-confirm!
(str "⚠️ Are you sure you want to delete this "
(if (entity-util/page? block) "page" "block")
"?"))
(p/then
(fn []
(mobile-ui/close-popup!)
(some->
(:block/uuid block)
(page-handler/<delete!
(fn [] (close!))
{:error-handler
(fn [{:keys [msg]}]
(notification/show! msg :warning))})))))}
[:span.text-lg.flex.gap-2.items-center.text-red-700
(shui/tabler-icon "trash" {:class "opacity-80" :size 22})
"Delete"])])
{:title "Actions"
:type :action-sheet}))}
(shui/tabler-icon "dots-vertical" {:size 18 :stroke 2})]]]
[:span.flex.items-center.gap-2
(when-let [block-id-str (str (:block/uuid block))]
[:a.active:opacity-80.pr-1
{:class (if favorited? "opacity-80 !text-yellow-800" "opacity-40")
:on-click #(-> (if favorited?
(page-handler/<unfavorite-page! block-id-str)
(page-handler/<favorite-page! block-id-str))
(p/then (fn [] (set-favorited! (not favorited?)))))}
(shui/tabler-icon (if favorited? "star-filled" "star") {:size 18 :stroke 2})])
[:a.opacity-40.active:opacity-60.pr-1
{:on-pointer-down (fn []
(mobile-ui/open-popup!
(fn []
[:div.-mx-2
(ui/menu-link
{:on-click #(mobile-ui/close-popup!)}
[:span.text-lg.flex.gap-2.items-center
(shui/tabler-icon "copy" {:class "opacity-80" :size 22})
"Copy"])
;; block page content
[:div.block-modal-page-content
(when open?
(mobile-ui/classic-app-container-wrap
(page/page-cp (db/entity [:block/uuid (:block/uuid block)]))))]])))))))))
(ui/menu-link
{:on-click #(-> (shui/dialog-confirm!
(str "⚠️ Are you sure you want to delete this "
(if (entity-util/page? block) "page" "block")
"?"))
(p/then
(fn []
(mobile-ui/close-popup!)
(some->
(:block/uuid block)
(page-handler/<delete!
(fn [] (close!))
{:error-handler
(fn [{:keys [msg]}]
(notification/show! msg :warning))})))))}
[:span.text-lg.flex.gap-2.items-center.text-red-700
(shui/tabler-icon "trash" {:class "opacity-80" :size 22})
"Delete"])])
{:title "Actions"
:type :action-sheet}))}
(shui/tabler-icon "dots-vertical" {:size 18 :stroke 2})]]]
;; block page content
[:div.block-modal-page-content
(when open?
(mobile-ui/classic-app-container-wrap
(page/page-cp (db/entity [:block/uuid (:block/uuid block)]))))]])))
))))))