mirror of
https://github.com/logseq/logseq.git
synced 2026-04-24 14:14:55 +00:00
enhance(mobile): adapt silkhq page components
This commit is contained in:
29
deps/shui/src/logseq/shui/silkhq.cljs
vendored
29
deps/shui/src/logseq/shui/silkhq.cljs
vendored
@@ -82,4 +82,31 @@
|
||||
(def stacking-sheet-outlet (silkhq-wrap "SheetWithStacking.Outlet"))
|
||||
(def stacking-sheet-backdrop (silkhq-wrap "SheetWithStacking.Backdrop"))
|
||||
(def stacking-sheet-view (silkhq-wrap "SheetWithStacking.View"))
|
||||
(def stacking-sheet-stack (silkhq-wrap "SheetWithStackingStack.Root"))
|
||||
(def stacking-sheet-stack (silkhq-wrap "SheetWithStackingStack.Root"))
|
||||
|
||||
(def parallax-page (silkhq-wrap "ParallaxPage.Root"))
|
||||
(def parallax-page-portal (silkhq-wrap "ParallaxPage.Portal"))
|
||||
(def parallax-page-handle (silkhq-wrap "ParallaxPage.Handle"))
|
||||
(def parallax-page-content (silkhq-wrap "ParallaxPage.Content"))
|
||||
(def parallax-page-description (silkhq-wrap "ParallaxPage.Description"))
|
||||
(def parallax-page-title (silkhq-wrap "ParallaxPage.Title"))
|
||||
(def parallax-page-trigger (silkhq-wrap "ParallaxPage.Trigger"))
|
||||
(def parallax-page-outlet (silkhq-wrap "ParallaxPage.Outlet"))
|
||||
(def parallax-page-backdrop (silkhq-wrap "ParallaxPage.Backdrop"))
|
||||
(def parallax-page-view (silkhq-wrap "ParallaxPage.View"))
|
||||
(def parallax-page-view-portal (silkhq-wrap "ParallaxPage.ViewPortal"))
|
||||
(def parallax-page-topbar-title (silkhq-wrap "ParallaxPage.TopBarTitle"))
|
||||
(def parallax-page-topbar-portal (silkhq-wrap "ParallaxPage.TopBarTitlePortal"))
|
||||
(def parallax-page-topbar-dismiss-trigger (silkhq-wrap "ParallaxPage.TopBarDismissTrigger"))
|
||||
(def parallax-page-topbar-dismiss-trigger-portal (silkhq-wrap "ParallaxPage.TopBarDismissTriggerPortal"))
|
||||
|
||||
(def card-sheet (silkhq-wrap "CardSheet.Root"))
|
||||
(def card-sheet-portal (silkhq-wrap "CardSheet.Portal"))
|
||||
(def card-sheet-handle (silkhq-wrap "CardSheet.Handle"))
|
||||
(def card-sheet-content (silkhq-wrap "CardSheet.Content"))
|
||||
(def card-sheet-title (silkhq-wrap "CardSheet.Title"))
|
||||
(def card-sheet-description (silkhq-wrap "CardSheet.Description"))
|
||||
(def card-sheet-trigger (silkhq-wrap "CardSheet.Trigger"))
|
||||
(def card-sheet-outlet (silkhq-wrap "CardSheet.Outlet"))
|
||||
(def card-sheet-backdrop (silkhq-wrap "CardSheet.Backdrop"))
|
||||
(def card-sheet-view (silkhq-wrap "CardSheet.View"))
|
||||
|
||||
18
packages/ui/src/silkhq/Card.css
Normal file
18
packages/ui/src/silkhq/Card.css
Normal file
@@ -0,0 +1,18 @@
|
||||
.Card-view {
|
||||
--card-radius: 36px;
|
||||
|
||||
/* SELF-LAYOUT */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.Card-content {
|
||||
/* SELF-LAYOUT */
|
||||
box-sizing: border-box;
|
||||
width: min(600px, calc(100% - 2 * 1rem));
|
||||
height: auto;
|
||||
|
||||
/* APPEARANCE */
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
border-radius: var(--card-radius);
|
||||
background-color: white;
|
||||
}
|
||||
119
packages/ui/src/silkhq/Card.tsx
Normal file
119
packages/ui/src/silkhq/Card.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React from 'react'
|
||||
import { Sheet } from '@silk-hq/components'
|
||||
import './Card.css'
|
||||
|
||||
// ================================================================================================
|
||||
// Root
|
||||
// ================================================================================================
|
||||
|
||||
type SheetRootProps = React.ComponentPropsWithoutRef<typeof Sheet.Root>;
|
||||
type CardRootProps = Omit<SheetRootProps, 'license'> & {
|
||||
license?: SheetRootProps['license'];
|
||||
};
|
||||
|
||||
const CardRoot = React.forwardRef<React.ElementRef<typeof Sheet.Root>, CardRootProps>(
|
||||
({ children, ...restProps }, ref) => {
|
||||
return (
|
||||
<Sheet.Root license="commercial" {...restProps} ref={ref}>
|
||||
{children}
|
||||
</Sheet.Root>
|
||||
)
|
||||
}
|
||||
)
|
||||
CardRoot.displayName = 'Card.Root'
|
||||
|
||||
// ================================================================================================
|
||||
// View
|
||||
// ================================================================================================
|
||||
|
||||
const CardView = React.forwardRef<
|
||||
React.ElementRef<typeof Sheet.View>,
|
||||
React.ComponentPropsWithoutRef<typeof Sheet.View>
|
||||
>(({ children, className, ...restProps }, ref) => {
|
||||
return (
|
||||
<Sheet.View
|
||||
className={`Card-view ${className ?? ''}`.trim()}
|
||||
contentPlacement="center"
|
||||
tracks="top"
|
||||
enteringAnimationSettings={{
|
||||
easing: 'spring',
|
||||
stiffness: 260,
|
||||
damping: 20,
|
||||
mass: 1,
|
||||
}}
|
||||
nativeEdgeSwipePrevention={true}
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</Sheet.View>
|
||||
)
|
||||
})
|
||||
CardView.displayName = 'Card.View'
|
||||
|
||||
// ================================================================================================
|
||||
// Backdrop
|
||||
// ================================================================================================
|
||||
|
||||
const CardBackdrop = React.forwardRef<
|
||||
React.ElementRef<typeof Sheet.Backdrop>,
|
||||
React.ComponentPropsWithoutRef<typeof Sheet.Backdrop>
|
||||
>(({ className, ...restProps }, ref) => {
|
||||
return (
|
||||
<Sheet.Backdrop
|
||||
className={`Card-backdrop ${className ?? ''}`.trim()}
|
||||
travelAnimation={{
|
||||
opacity: ({ progress }) => Math.min(0.4 * progress, 0.4),
|
||||
}}
|
||||
themeColorDimming="auto"
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
})
|
||||
CardBackdrop.displayName = 'Card.Backdrop'
|
||||
|
||||
// ================================================================================================
|
||||
// Content
|
||||
// ================================================================================================
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
React.ElementRef<typeof Sheet.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof Sheet.Content>
|
||||
>(({ children, className, ...restProps }, ref) => {
|
||||
return (
|
||||
<Sheet.Content
|
||||
className={`Card-content ${className ?? ''}`.trim()}
|
||||
travelAnimation={{ scale: [0.8, 1] }}
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</Sheet.Content>
|
||||
)
|
||||
})
|
||||
CardContent.displayName = 'Card.Content'
|
||||
|
||||
// ================================================================================================
|
||||
// Unchanged Components
|
||||
// ================================================================================================
|
||||
|
||||
const CardPortal = Sheet.Portal
|
||||
const CardTrigger = Sheet.Trigger
|
||||
const CardHandle = Sheet.Handle
|
||||
const CardOutlet = Sheet.Outlet
|
||||
const CardTitle = Sheet.Title
|
||||
const CardDescription = Sheet.Description
|
||||
|
||||
export const Card = {
|
||||
Root: CardRoot,
|
||||
Portal: CardPortal,
|
||||
View: CardView,
|
||||
Backdrop: CardBackdrop,
|
||||
Content: CardContent,
|
||||
Trigger: CardTrigger,
|
||||
Handle: CardHandle,
|
||||
Outlet: CardOutlet,
|
||||
Title: CardTitle,
|
||||
Description: CardDescription,
|
||||
}
|
||||
99
packages/ui/src/silkhq/ParallaxPage.css
Normal file
99
packages/ui/src/silkhq/ParallaxPage.css
Normal file
@@ -0,0 +1,99 @@
|
||||
/* Stack top bar elements */
|
||||
|
||||
.ParallaxPage-stackTopBarDismissTriggerContainer {
|
||||
/* INNER-LAYOUT */
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.ParallaxPage-stackTopBarTitleContainer {
|
||||
/* INNER-LAYOUT */
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
/* Page */
|
||||
|
||||
.ParallaxPage-view {
|
||||
--header-height: 49px;
|
||||
|
||||
/* SELF-LAYOUT */
|
||||
/* Adding 60px to make it fully visible below iOS Safari's bottom UI */
|
||||
/* Subtracting 1px to avoid sub-pixel gap in Safari */
|
||||
height: calc(
|
||||
var(--silk-100-lvh-dvh-pct) + 60px -
|
||||
(env(safe-area-inset-top, 0px) + var(--header-height) + 1px)
|
||||
);
|
||||
top: calc(env(safe-area-inset-top, 0px) + var(--header-height) - 1px);
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
.ParallaxPage-view {
|
||||
height: calc(var(--silk-100-lvh-dvh-pct) - var(--header-height));
|
||||
}
|
||||
}
|
||||
|
||||
.ParallaxPage-content {
|
||||
/* SELF-LAYOUT */
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
|
||||
/* APPEARANCE */
|
||||
background-color: white;
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
/* Page top bar elements */
|
||||
|
||||
.ParallaxPage-topBarDismissTrigger {
|
||||
/* SELF-LAYOUT */
|
||||
grid-area: 1 / 1;
|
||||
align-self: center;
|
||||
margin-left: -14px;
|
||||
width: 36px;
|
||||
height: 44px;
|
||||
|
||||
/* APPEARANCE */
|
||||
border-radius: 12px;
|
||||
outline-offset: -5px;
|
||||
appearance: none;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
/* INTERACTIVITY */
|
||||
cursor: pointer;
|
||||
|
||||
/* TRANSFORMATION */
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
|
||||
/* INNER-LAYOUT */
|
||||
padding: 0;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ParallaxPage-topBarDismissIcon {
|
||||
/* SELF-LAYOUT */
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
/* APPEARANCE */
|
||||
color: rgb(75, 85, 99);
|
||||
}
|
||||
|
||||
.ParallaxPage-topBarTitle {
|
||||
/* SELF-LAYOUT */
|
||||
grid-area: 1 / 1;
|
||||
max-width: 100%;
|
||||
|
||||
/* APPEARANCE */
|
||||
opacity: 0;
|
||||
|
||||
/* TEXT */
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
font-size: 17.25px;
|
||||
font-weight: 620;
|
||||
color: rgb(31, 41, 55);
|
||||
}
|
||||
464
packages/ui/src/silkhq/ParallaxPage.tsx
Normal file
464
packages/ui/src/silkhq/ParallaxPage.tsx
Normal file
@@ -0,0 +1,464 @@
|
||||
import React, { createContext, useContext, useRef, useState } from 'react'
|
||||
import { createComponentId, Island, Sheet, SheetStack } from '@silk-hq/components'
|
||||
import './ParallaxPage.css'
|
||||
|
||||
// ================================================================================================
|
||||
// Utils
|
||||
// ================================================================================================
|
||||
|
||||
const setRefs = <T, >(...refs: (React.Ref<T> | undefined)[]): ((node: T) => void) => {
|
||||
return (node: T) => {
|
||||
refs.forEach((ref) => {
|
||||
if (typeof ref === 'function') {
|
||||
ref(node)
|
||||
} else if (ref) {
|
||||
// @ts-ignore - intentionally breaking the readonly nature for compatibility
|
||||
ref.current = node
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// Stack Id
|
||||
// ================================================================================================
|
||||
|
||||
const ParallaxPageExampleStackId = createComponentId()
|
||||
|
||||
// ================================================================================================
|
||||
// StackRoot Context
|
||||
// ================================================================================================
|
||||
|
||||
type ParallaxPageStackRootContextValue = {
|
||||
pageContainer: HTMLElement | null;
|
||||
setPageContainer: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
|
||||
dismissTriggerContainerRef: React.RefObject<HTMLDivElement>;
|
||||
topBarTitleContainerRef: React.RefObject<HTMLDivElement>;
|
||||
};
|
||||
|
||||
const ParallaxPageStackRootContext = createContext<ParallaxPageStackRootContextValue | null>(null)
|
||||
const useParallaxPageStackRootContext = () => {
|
||||
const context = useContext(ParallaxPageStackRootContext)
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useParallaxPageStackRootContext must be used within a ParallaxPageStackRootContext'
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// Stack Root
|
||||
// ================================================================================================
|
||||
|
||||
// In this component, the stack root is designed to receive the
|
||||
// parallax page Views.
|
||||
|
||||
const ParallaxPageStackRoot = React.forwardRef<
|
||||
React.ElementRef<typeof SheetStack.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetStack.Root>
|
||||
>(({ componentId, ...restProps }, ref) => {
|
||||
const [pageContainer, setPageContainer] = useState<HTMLElement | null>(null)
|
||||
const dismissTriggerContainerRef = useRef<HTMLDivElement>(null)
|
||||
const topBarTitleContainerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
return (
|
||||
<ParallaxPageStackRootContext.Provider
|
||||
value={{
|
||||
pageContainer,
|
||||
setPageContainer,
|
||||
dismissTriggerContainerRef,
|
||||
topBarTitleContainerRef,
|
||||
}}
|
||||
>
|
||||
<SheetStack.Root
|
||||
// Using a componentId to associate the SheetStack
|
||||
// with the ParallaxPageStackIslandRoot
|
||||
componentId={componentId ?? ParallaxPageExampleStackId}
|
||||
{...restProps}
|
||||
ref={setRefs(ref, setPageContainer)}
|
||||
/>
|
||||
</ParallaxPageStackRootContext.Provider>
|
||||
)
|
||||
})
|
||||
ParallaxPageStackRoot.displayName = 'ParallaxPageStack.Root'
|
||||
|
||||
// ================================================================================================
|
||||
// Stack Scenery Outlet
|
||||
// ================================================================================================
|
||||
|
||||
// An outlet meant to wrap the content below the stack for the
|
||||
// initial parallax effect.
|
||||
|
||||
const ParallaxPageStackSceneryOutlet = React.forwardRef<
|
||||
React.ElementRef<typeof SheetStack.Outlet>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetStack.Outlet>
|
||||
>((props, ref) => {
|
||||
return (
|
||||
<SheetStack.Outlet
|
||||
stackingAnimation={{
|
||||
transformOrigin: '50% 0px',
|
||||
translateX: ({ progress }) => (progress <= 1 ? progress * -80 + 'px' : '-80px'),
|
||||
}}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
})
|
||||
ParallaxPageStackSceneryOutlet.displayName = 'ParallaxPageStack.SceneryOutlet'
|
||||
|
||||
// ================================================================================================
|
||||
// Stack Island Root
|
||||
// ================================================================================================
|
||||
|
||||
// An Island meant to wrap the top bar, but which can be used
|
||||
// elsewhere as well.
|
||||
|
||||
const ParallaxPageStackIslandRoot = React.forwardRef<
|
||||
React.ElementRef<typeof Island.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof Island.Root>
|
||||
>(({ forComponent, ...restProps }, ref) => {
|
||||
return (
|
||||
<Island.Root
|
||||
forComponent={forComponent ?? ParallaxPageExampleStackId}
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
})
|
||||
ParallaxPageStackIslandRoot.displayName = 'ParallaxPageStack.IslandRoot'
|
||||
|
||||
// ================================================================================================
|
||||
// Stack Island Content
|
||||
// ================================================================================================
|
||||
|
||||
const ParallaxPageStackIslandContent = Island.Content
|
||||
|
||||
// ================================================================================================
|
||||
// Stack Top Bar Dismiss Trigger Container
|
||||
// ================================================================================================
|
||||
|
||||
// A container meant to receive the parallax pages dismiss
|
||||
// triggers in the top bar.
|
||||
|
||||
const ParallaxPageStackTopBarDismissTriggerContainer = ({
|
||||
className,
|
||||
...restProps
|
||||
}: React.ComponentPropsWithoutRef<'div'>) => {
|
||||
const { dismissTriggerContainerRef } = useParallaxPageStackRootContext()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ParallaxPage-stackTopBarDismissTriggerContainer ${className ?? ''}`.trim()}
|
||||
{...restProps}
|
||||
ref={dismissTriggerContainerRef}
|
||||
/>
|
||||
)
|
||||
}
|
||||
ParallaxPageStackTopBarDismissTriggerContainer.displayName =
|
||||
'ParallaxPageStack.TopBarDismissTriggerContainer'
|
||||
|
||||
// ================================================================================================
|
||||
// Stack Top Bar Title Outlet
|
||||
// ================================================================================================
|
||||
|
||||
// An outlet meant to wrap the initial title of the top bar.
|
||||
|
||||
const ParallaxPageStackTopBarTitleOutlet = React.forwardRef<
|
||||
React.ElementRef<typeof SheetStack.Outlet>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetStack.Outlet>
|
||||
>(({ stackingAnimation, ...restProps }, ref) => {
|
||||
return (
|
||||
<SheetStack.Outlet
|
||||
stackingAnimation={{
|
||||
opacity: ({ progress }) => 0.75 - (1 / 0.75) * (progress - 0.25),
|
||||
...stackingAnimation,
|
||||
}}
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
})
|
||||
ParallaxPageStackTopBarTitleOutlet.displayName = 'ParallaxPageStack.TopBarTitleOutlet'
|
||||
|
||||
// ================================================================================================
|
||||
// Stack Top Bar Title Container
|
||||
// ================================================================================================
|
||||
|
||||
// A container meant to receive the parallax pages titles in the
|
||||
// top bar.
|
||||
|
||||
const ParallaxPageStackTopBarTitleContainer = ({
|
||||
className,
|
||||
...restProps
|
||||
}: React.ComponentPropsWithoutRef<'div'>) => {
|
||||
const { topBarTitleContainerRef } = useParallaxPageStackRootContext()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ParallaxPage-stackTopBarTitleContainer ${className ?? ''}`.trim()}
|
||||
{...restProps}
|
||||
ref={topBarTitleContainerRef}
|
||||
/>
|
||||
)
|
||||
}
|
||||
ParallaxPageStackTopBarTitleContainer.displayName = 'ParallaxPageStack.TopBarTitleContainer'
|
||||
|
||||
// ================================================================================================
|
||||
// Root
|
||||
// ================================================================================================
|
||||
|
||||
type SheetRootProps = React.ComponentPropsWithoutRef<typeof Sheet.Root>;
|
||||
type ParallaxPageRootProps = Omit<SheetRootProps, 'license'> & {
|
||||
license?: SheetRootProps['license'];
|
||||
};
|
||||
|
||||
const ParallaxPageRoot = React.forwardRef<
|
||||
React.ElementRef<typeof Sheet.Root>,
|
||||
ParallaxPageRootProps
|
||||
>((props, ref) => {
|
||||
return (
|
||||
<Sheet.Root
|
||||
license="commercial"
|
||||
// By default, the Sheet will be associated with the
|
||||
// closest SheetStack
|
||||
forComponent="closest"
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
})
|
||||
ParallaxPageRoot.displayName = 'ParallaxPage.Root'
|
||||
|
||||
// ================================================================================================
|
||||
// View Portal
|
||||
// ================================================================================================
|
||||
|
||||
// A portal that will render the View in the stack root by
|
||||
// default.
|
||||
|
||||
const ParallaxPageViewPortal = (props: React.ComponentPropsWithoutRef<typeof Sheet.Portal>) => {
|
||||
const { pageContainer } = useParallaxPageStackRootContext()
|
||||
|
||||
return <Sheet.Portal container={pageContainer as HTMLElement} {...props} />
|
||||
}
|
||||
ParallaxPageViewPortal.displayName = 'ParallaxPage.ViewPortal'
|
||||
|
||||
// ================================================================================================
|
||||
// View
|
||||
// ================================================================================================
|
||||
|
||||
const ParallaxPageView = React.forwardRef<
|
||||
React.ElementRef<typeof Sheet.View>,
|
||||
React.ComponentPropsWithoutRef<typeof Sheet.View>
|
||||
>(
|
||||
(
|
||||
{ className, contentPlacement, swipeOvershoot, nativeEdgeSwipePrevention, ...restProps },
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<Sheet.View
|
||||
className={`ParallaxPage-view ${className ?? ''}`.trim()}
|
||||
contentPlacement={contentPlacement ?? 'right'}
|
||||
swipeOvershoot={swipeOvershoot ?? false}
|
||||
nativeEdgeSwipePrevention={nativeEdgeSwipePrevention ?? true}
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
ParallaxPageView.displayName = 'ParallaxPage.View'
|
||||
|
||||
// ================================================================================================
|
||||
// Backdrop
|
||||
// ================================================================================================
|
||||
|
||||
const ParallaxPageBackdrop = React.forwardRef<
|
||||
React.ElementRef<typeof Sheet.Backdrop>,
|
||||
React.ComponentPropsWithoutRef<typeof Sheet.Backdrop>
|
||||
>(({ travelAnimation, ...restProps }, ref) => {
|
||||
return (
|
||||
<Sheet.Backdrop
|
||||
travelAnimation={{ opacity: [0, 0.25], ...travelAnimation }}
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
ParallaxPageBackdrop.displayName = 'ParallaxPage.Backdrop'
|
||||
|
||||
// ================================================================================================
|
||||
// Content
|
||||
// ================================================================================================
|
||||
|
||||
const ParallaxPageContent = React.forwardRef<
|
||||
React.ElementRef<typeof Sheet.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof Sheet.Content>
|
||||
>(({ stackingAnimation, ...restProps }, ref) => {
|
||||
return (
|
||||
<Sheet.Content
|
||||
className="ParallaxPage-content"
|
||||
stackingAnimation={{
|
||||
translateX: ({ progress }) => (progress <= 1 ? progress * -80 + 'px' : '-80px'),
|
||||
...stackingAnimation,
|
||||
}}
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
})
|
||||
ParallaxPageContent.displayName = 'ParallaxPage.Content'
|
||||
|
||||
// ================================================================================================
|
||||
// Top Bar Dismiss Trigger Portal
|
||||
// ================================================================================================
|
||||
|
||||
// A portal that will render the dismiss trigger in the top bar
|
||||
// dismiss trigger container by default.
|
||||
|
||||
const ParallaxPageTopBarDismissTriggerPortal = ({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof Sheet.Portal>) => {
|
||||
const { dismissTriggerContainerRef } = useParallaxPageStackRootContext()
|
||||
|
||||
return (
|
||||
<Sheet.Portal container={dismissTriggerContainerRef.current as HTMLElement} {...props}>
|
||||
{children}
|
||||
</Sheet.Portal>
|
||||
)
|
||||
}
|
||||
ParallaxPageTopBarDismissTriggerPortal.displayName = 'ParallaxPage.TopBarDismissTriggerPortal'
|
||||
|
||||
// ================================================================================================
|
||||
// Top Bar Dismiss Trigger
|
||||
// ================================================================================================
|
||||
|
||||
// The top bar dismiss trigger associated with the parallax page.
|
||||
|
||||
const ParallaxPageTopBarDismissTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof Sheet.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof Sheet.Trigger>
|
||||
>(({ className, action, travelAnimation, ...restProps }, ref) => {
|
||||
return (
|
||||
<Sheet.Trigger
|
||||
className={`ParallaxPage-topBarDismissTrigger ${className ?? ''}`.trim()}
|
||||
action={action ?? 'dismiss'}
|
||||
travelAnimation={{
|
||||
visibility: 'visible',
|
||||
opacity: ({ progress }) => (1 / 0.75) * (progress - 0.25),
|
||||
...travelAnimation,
|
||||
}}
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
className="ParallaxPage-topBarDismissIcon"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</Sheet.Trigger>
|
||||
)
|
||||
})
|
||||
ParallaxPageTopBarDismissTrigger.displayName = 'ParallaxPage.TopBarDismissTrigger'
|
||||
|
||||
// ================================================================================================
|
||||
// Top Bar Title Portal
|
||||
// ================================================================================================
|
||||
|
||||
// A portal that will render the top bar title in the top bar
|
||||
// title container by default.
|
||||
|
||||
const ParallaxPageTopBarTitlePortal = ({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof Sheet.Outlet>) => {
|
||||
const { topBarTitleContainerRef } = useParallaxPageStackRootContext()
|
||||
|
||||
return (
|
||||
<Sheet.Portal container={topBarTitleContainerRef.current as HTMLElement} {...props}>
|
||||
{children}
|
||||
</Sheet.Portal>
|
||||
)
|
||||
}
|
||||
ParallaxPageTopBarTitlePortal.displayName = 'ParallaxPage.TopBarTitlePortal'
|
||||
|
||||
// ================================================================================================
|
||||
// Top Bar Title
|
||||
// ================================================================================================
|
||||
|
||||
// The top bar title associated with the parallax page.
|
||||
|
||||
const ParallaxPageTopBarTitle = React.forwardRef<
|
||||
React.ElementRef<typeof Sheet.Outlet>,
|
||||
React.ComponentPropsWithoutRef<typeof Sheet.Outlet>
|
||||
>(({ className, travelAnimation, stackingAnimation, ...restProps }, ref) => {
|
||||
return (
|
||||
<Sheet.Outlet
|
||||
className={`ParallaxPage-topBarTitle ${className ?? ''}`.trim()}
|
||||
travelAnimation={{
|
||||
opacity: ({ progress }) => (1 / 0.75) * (progress - 0.25),
|
||||
...travelAnimation,
|
||||
}}
|
||||
stackingAnimation={{
|
||||
opacity: ({ progress }) => 0.75 - (1 / 0.75) * (progress - 0.25),
|
||||
...stackingAnimation,
|
||||
}}
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
})
|
||||
ParallaxPageTopBarTitle.displayName = 'ParallaxPage.TopBarTitle'
|
||||
|
||||
// ================================================================================================
|
||||
// Unchanged components
|
||||
// ================================================================================================
|
||||
|
||||
const ParallaxPagePortal = Sheet.Portal
|
||||
const ParallaxPageTrigger = Sheet.Trigger
|
||||
const ParallaxPageHandle = Sheet.Handle
|
||||
const ParallaxPageOutlet = Sheet.Outlet
|
||||
const ParallaxPageTitle = Sheet.Title
|
||||
const ParallaxPageDescription = Sheet.Description
|
||||
|
||||
export const ParallaxPageStack = {
|
||||
// Stack
|
||||
Root: ParallaxPageStackRoot,
|
||||
SceneryOutlet: ParallaxPageStackSceneryOutlet,
|
||||
// Stack top bar components
|
||||
IslandRoot: ParallaxPageStackIslandRoot,
|
||||
IslandContent: ParallaxPageStackIslandContent,
|
||||
TopBarDismissTriggerContainer: ParallaxPageStackTopBarDismissTriggerContainer,
|
||||
TopBarTitleOutlet: ParallaxPageStackTopBarTitleOutlet,
|
||||
TopBarTitleContainer: ParallaxPageStackTopBarTitleContainer,
|
||||
}
|
||||
|
||||
export const ParallaxPage = {
|
||||
// Page
|
||||
Root: ParallaxPageRoot,
|
||||
ViewPortal: ParallaxPageViewPortal,
|
||||
View: ParallaxPageView,
|
||||
Backdrop: ParallaxPageBackdrop,
|
||||
Content: ParallaxPageContent,
|
||||
Trigger: ParallaxPageTrigger,
|
||||
Handle: ParallaxPageHandle,
|
||||
Outlet: ParallaxPageOutlet,
|
||||
Portal: ParallaxPagePortal,
|
||||
Title: ParallaxPageTitle,
|
||||
Description: ParallaxPageDescription,
|
||||
// Top Bar page components
|
||||
TopBarDismissTriggerPortal: ParallaxPageTopBarDismissTriggerPortal,
|
||||
TopBarDismissTrigger: ParallaxPageTopBarDismissTrigger,
|
||||
TopBarTitlePortal: ParallaxPageTopBarTitlePortal,
|
||||
TopBarTitle: ParallaxPageTopBarTitle,
|
||||
}
|
||||
63
packages/ui/src/silkhq/Toast.css
Normal file
63
packages/ui/src/silkhq/Toast.css
Normal file
@@ -0,0 +1,63 @@
|
||||
.Toast-container {
|
||||
/* SELF-LAYOUT */
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.Toast-view {
|
||||
--padding-x: 0.75rem;
|
||||
--padding-top: 0.75rem;
|
||||
--padding-bottom: 2.75rem;
|
||||
--max-height: 90px;
|
||||
--contentHeight: calc(
|
||||
max(env(safe-area-inset-top, 0px), var(--padding-top)) + var(--max-height) +
|
||||
var(--padding-bottom)
|
||||
);
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
.Toast-view {
|
||||
--padding-x: 0.875rem;
|
||||
--padding-top: 0.875rem;
|
||||
|
||||
/* SELF-LAYOUT */
|
||||
height: var(--contentHeight);
|
||||
}
|
||||
}
|
||||
|
||||
.Toast-content {
|
||||
/* SELF-LAYOUT */
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
height: var(--contentHeight);
|
||||
|
||||
/* APPEARANCE */
|
||||
background-color: transparent;
|
||||
|
||||
/* INNER-LAYOUT */
|
||||
padding: var(--padding-x);
|
||||
padding-top: max(env(safe-area-inset-top), var(--padding-top));
|
||||
padding-bottom: var(--padding-bottom);
|
||||
display: grid;
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
.Toast-content {
|
||||
/* SELF-LAYOUT */
|
||||
max-width: 400px;
|
||||
|
||||
/* INNER-LAYOUT */
|
||||
padding-top: var(--padding-top);
|
||||
}
|
||||
}
|
||||
|
||||
.Toast-innerContent {
|
||||
/* SELF-LAYOUT */
|
||||
max-height: var(--max-height);
|
||||
|
||||
/* APPEARANCE */
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgb(240, 240, 240);
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: saturate(5) blur(20px);
|
||||
}
|
||||
181
packages/ui/src/silkhq/Toast.tsx
Normal file
181
packages/ui/src/silkhq/Toast.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
import React, { useEffect, useState, useRef, createContext, useContext } from 'react'
|
||||
import { Sheet, useClientMediaQuery } from '@silk-hq/components'
|
||||
import './Toast.css'
|
||||
|
||||
// ================================================================================================
|
||||
// Context
|
||||
// ================================================================================================
|
||||
|
||||
type ToastContextValue = {
|
||||
presented: boolean;
|
||||
setPresented: (presented: boolean) => void;
|
||||
pointerOver: boolean;
|
||||
setPointerOver: (pointerOver: boolean) => void;
|
||||
travelStatus: string;
|
||||
setTravelStatus: (status: string) => void;
|
||||
};
|
||||
|
||||
const ToastContext = createContext<ToastContextValue | null>(null)
|
||||
|
||||
// ================================================================================================
|
||||
// Root
|
||||
// ================================================================================================
|
||||
|
||||
type SheetRootProps = React.ComponentPropsWithoutRef<typeof Sheet.Root>;
|
||||
type ToastRootProps = Omit<SheetRootProps, 'license'> & {
|
||||
license?: SheetRootProps['license'];
|
||||
};
|
||||
|
||||
const ToastRoot = React.forwardRef<React.ElementRef<typeof Sheet.Root>, ToastRootProps>(
|
||||
({ children, ...restProps }, ref) => {
|
||||
const [presented, setPresented] = useState(false)
|
||||
const [pointerOver, setPointerOver] = useState(false)
|
||||
const [travelStatus, setTravelStatus] = useState('idleOutside')
|
||||
const autoCloseTimeout = useRef<ReturnType<typeof setTimeout> | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
const startAutoCloseTimeout = () => {
|
||||
autoCloseTimeout.current = setTimeout(() => setPresented(false), 5000)
|
||||
}
|
||||
|
||||
const clearAutoCloseTimeout = () => {
|
||||
clearTimeout(autoCloseTimeout.current)
|
||||
}
|
||||
|
||||
if (presented) {
|
||||
if (travelStatus === 'idleInside' && !pointerOver) {
|
||||
startAutoCloseTimeout()
|
||||
} else {
|
||||
clearAutoCloseTimeout()
|
||||
}
|
||||
}
|
||||
return clearAutoCloseTimeout
|
||||
}, [presented, travelStatus, pointerOver])
|
||||
|
||||
return (
|
||||
<ToastContext.Provider
|
||||
value={{
|
||||
presented,
|
||||
setPresented,
|
||||
pointerOver,
|
||||
setPointerOver,
|
||||
travelStatus,
|
||||
setTravelStatus,
|
||||
}}
|
||||
>
|
||||
<Sheet.Root
|
||||
license="commercial"
|
||||
presented={presented}
|
||||
onPresentedChange={setPresented}
|
||||
sheetRole=""
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</Sheet.Root>
|
||||
</ToastContext.Provider>
|
||||
)
|
||||
}
|
||||
)
|
||||
ToastRoot.displayName = 'Toast.Root'
|
||||
|
||||
// ================================================================================================
|
||||
// View
|
||||
// ================================================================================================
|
||||
|
||||
const ToastView = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentPropsWithoutRef<typeof Sheet.View>
|
||||
>(({ children, className, ...restProps }, ref) => {
|
||||
const largeViewport = useClientMediaQuery('(min-width: 1000px)')
|
||||
const contentPlacement = largeViewport ? 'right' : 'top'
|
||||
|
||||
const context = useContext(ToastContext)
|
||||
if (!context) throw new Error('ToastView must be used within a ToastContext.Provider')
|
||||
const { setTravelStatus } = context
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`Toast-container ${className ?? ''}`.trim()}
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
>
|
||||
<Sheet.View
|
||||
className={`Toast-view ${className ?? ''}`.trim()}
|
||||
contentPlacement={contentPlacement}
|
||||
inertOutside={false}
|
||||
onPresentAutoFocus={{ focus: false }}
|
||||
onDismissAutoFocus={{ focus: false }}
|
||||
onClickOutside={{
|
||||
dismiss: false,
|
||||
stopOverlayPropagation: false,
|
||||
}}
|
||||
onEscapeKeyDown={{
|
||||
dismiss: false,
|
||||
stopOverlayPropagation: false,
|
||||
}}
|
||||
onTravelStatusChange={setTravelStatus}
|
||||
>
|
||||
{children}
|
||||
</Sheet.View>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
ToastView.displayName = 'Toast.View'
|
||||
|
||||
// ================================================================================================
|
||||
// Content
|
||||
// ================================================================================================
|
||||
|
||||
const ToastContent = React.forwardRef<
|
||||
React.ElementRef<typeof Sheet.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof Sheet.Content>
|
||||
>(({ children, className, ...restProps }, ref) => {
|
||||
const context = useContext(ToastContext)
|
||||
if (!context) throw new Error('ToastContent must be used within ToastRoot')
|
||||
|
||||
return (
|
||||
<Sheet.Content
|
||||
className={`Toast-content ${className ?? ''}`.trim()}
|
||||
asChild
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
>
|
||||
<Sheet.SpecialWrapper.Root>
|
||||
<Sheet.SpecialWrapper.Content
|
||||
className="Toast-innerContent"
|
||||
onPointerEnter={() => context.setPointerOver(true)}
|
||||
onPointerLeave={() => context.setPointerOver(false)}
|
||||
>
|
||||
{children}
|
||||
</Sheet.SpecialWrapper.Content>
|
||||
</Sheet.SpecialWrapper.Root>
|
||||
</Sheet.Content>
|
||||
)
|
||||
})
|
||||
ToastContent.displayName = 'Toast.Content'
|
||||
|
||||
// ================================================================================================
|
||||
// Unchanged Components
|
||||
// ================================================================================================
|
||||
|
||||
const ToastPortal = Sheet.Portal
|
||||
const ToastTrigger = Sheet.Trigger
|
||||
const ToastHandle = Sheet.Handle
|
||||
const ToastOutlet = Sheet.Outlet
|
||||
const ToastTitle = Sheet.Title
|
||||
const ToastDescription = Sheet.Description
|
||||
|
||||
export const Toast = {
|
||||
Root: ToastRoot,
|
||||
Portal: ToastPortal,
|
||||
View: ToastView,
|
||||
Content: ToastContent,
|
||||
Trigger: ToastTrigger,
|
||||
Title: ToastTitle,
|
||||
Description: ToastDescription,
|
||||
Handle: ToastHandle,
|
||||
Outlet: ToastOutlet,
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import "@silk-hq/components/dist/main-unlayered.css"
|
||||
import '@silk-hq/components/dist/main-unlayered.css'
|
||||
import { Fixed, Scroll, Sheet } from '@silk-hq/components'
|
||||
import { BottomSheet } from './BottomSheet'
|
||||
import { SheetWithDepth, SheetWithDepthStack } from './SheetWithDepth'
|
||||
import { SheetWithDetent } from './SheetWithDetent'
|
||||
import { SheetWithStacking, SheetWithStackingStack } from './SheetWithStacking'
|
||||
import { ParallaxPage } from './ParallaxPage'
|
||||
import { Toast } from './Toast'
|
||||
import { Card } from './Card'
|
||||
|
||||
declare global {
|
||||
var LSSilkhq: any
|
||||
@@ -13,7 +16,8 @@ const silkhq = {
|
||||
Sheet, Fixed, Scroll, BottomSheet,
|
||||
SheetWithDepth, SheetWithDepthStack,
|
||||
SheetWithStacking, SheetWithDetent,
|
||||
SheetWithStackingStack,
|
||||
SheetWithStackingStack, ParallaxPage,
|
||||
Toast, CardSheet: Card,
|
||||
}
|
||||
|
||||
window.LSSilkhq = silkhq
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
--ls-page-title-size: 26px;
|
||||
}
|
||||
|
||||
html.is-native-ios {
|
||||
--safe-area-inset-top: 59px;
|
||||
--safe-area-inset-bottom: 16px;
|
||||
}
|
||||
|
||||
html.is-native-android {
|
||||
--safe-area-inset-top: 46px;
|
||||
--safe-area-inset-bottom: 16px;
|
||||
}
|
||||
|
||||
html.plt-capacitor.plt-android {
|
||||
--ion-safe-area-top: 42px;
|
||||
--ion-safe-area-bottom: 16px;
|
||||
@@ -345,5 +355,7 @@ html[data-color=logseq] {
|
||||
|
||||
.app-silk-index-container {
|
||||
@apply p-4 flex flex-col gap-3;
|
||||
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
(silkhq/stacking-sheet-trigger
|
||||
(shui/button "open: nested stacking sheet"))
|
||||
(silkhq/stacking-sheet-portal
|
||||
(stacking-view-example {:nested? true}))))])))
|
||||
(stacking-view-example {:nested? false}))))])))
|
||||
|
||||
(rum/defc silkhq-demos-page
|
||||
[]
|
||||
@@ -54,7 +54,7 @@
|
||||
:nativePageScrollReplacement true}
|
||||
(silkhq/scroll-content {:class "app-silk-index-scroll-content"}
|
||||
[:div.app-silk-index-container
|
||||
[:h2.text-lg.font-semibold "Silk sheets demos"]
|
||||
[:h2.text-xl.font-semibold.pt-4 "Silk sheets demos"]
|
||||
|
||||
;; Bottom Sheet case
|
||||
(silkhq/bottom-sheet
|
||||
|
||||
Reference in New Issue
Block a user